Monday 7 December 2015

SSRS Open Linked Report In New Window with Javascript

I recently wanted to set op a series of interlinked reports using SQL Server Reporting Services (SSRS) targetting a 2012 instance. I used javascript in a "Custom URL" action on a field to create the link.  Everything looked OK, but further testing I noticed the following type of error was often being generated in in the drill through report:

The report execution xnf441454bs2oh45hqvrkd45 has expired or cannot be found. (rsExecutionNotFound)


The problem was the parameter:  I was using a text field to pass through information for the parameter, and this textual information could include an Ampersand (&).

This is obviously a reserved character, so requires escaping.

To do this, we must utilise SSRS's commands REPLACE and ESCAPE in the custom action.  The easiest way to explain this is in this example:

="javascript:void window.open('" & Globals!ReportServerUrl & "/ReportServer/Pages/ReportViewer.aspx?%2fPhonebook+Reports%2fDepartment&department="
+Replace(Fields!Team.Value,"&","' + escape('&') + '") & "&location="+Replace(Fields!Location.Value,"&","' + escape('&') + '")
 & "&rs:Command=Render=','_blank','')"

Using this format to build the link, I can now open "drill through" reports with a parameter that contains an ampersand, and no longer get the "report execution" error above.   Nice.

Festina Limited Edition Chrono Bike 2015 Men’s Quartz Watch with Brown Dial F16883/1

Click here for a list of the best selling Festina watches this week.

Sunday 14 December 2014

Using .htaccess file to forbid spammers access to a URL

This is pretty straightforward, but I struggled to find a simple explanation of this and worked it out with trial and error, so thought I might as well post it here in case it helps somebody else :)

I run another website which I is hosted on Linux/Apache, and for a while I ran a forum on it. However the forum was never used, apart from spam bots, so I removed it. I later discovered that huge amounts of bandwidth was being taken up by spam bots trying to spam the now non-existent forum, each one being served a nice 404 message and taking up processing time on my site, so I decided to forbid access to it.

The forum sat at: http:////forum/index.php, and both /forum and /forum/index.php were getting lots of traffic. 

To block this I edited the Apache .htaccess file, and added this line:
 
RewriteRule ^forum($|/) - [F]

This is a rewrite rule, for any path after /forum, that results in the following "403" forbidden message:
 
Forbidden

You don't have permission to access /forum on this server.

This is exceptionally lightweight and does not trigger a 404. Problem solved!

If you want do a simple redirect, perhaps becuase you have changed your structure, this is even more simple.  Just use teh "Redirect 301" command as per the example below:

Redirect 301 /top-tens/bestselling-electronics/bestselling-televisions/bestselling-sony-televisions/ http://www.mysite.co.uk/top-tens/electronic/bestselling-televisions/
Redirect 301 /top-ten/watches/omega/ladies http://www.mysite.co.uk/top-tens/best-selling-omega-watches/


Please note that the "from" URL must be a relative URL, whilst the "to" URL is the full URL including domain.

Carissima Gold 9 ct Yellow Gold Two-Row Diamond Cut Curb Bracelet of 21 cm 8.5-inch on www.yngoo.co.uk
Carissima Gold 9 ct Yellow Gold Two-Row Diamond Cut Curb Bracelet of 21 cm/8.5-inch

Thursday 18 September 2014

Programatically overriding SharePoint record locks using Records.BypassLocks() in c#

Records are good - they are immutable and cannot be changed by users.

Record Centres are also good - they provide a repository for records that users can submit their documents to, to be transformed into records.

Event receiver rules that automatically commit as records any document submitted to a list are also good, because as soon as content is placed in that list it becomes a records, and so immutable.

However...

What if something goes wrong?  Perhaps a users submits a document that should not be locked as a record ans should be removed, or perhaps the wrong meta data is attached to the record?

In these cases an admin has to temporarily suspend the records management features of SharePoint in order to rectify the fault.  This is a big deal since SharePoint admins have a different role to server admins, and so normally would not have the authority (or indeed the know-how) to do this, whereas the server admins would not be able to understand the records and content.  In my experience the only people who know both how the system works, and what it is supposed to do, are the developers!  And we do NOT want to do admin, because among other things they somehow get paid more than we do...

Furthermore, if you are automatically declaring documents as records when they enter the list, you will not be able to "undeclare" the record from the SharePoint front end without first disabling this feature - quite a risk if you end up having to do this a lot.

This is why I like to create my own tools to fill the gaps, and in this case we programmers can turn to a little known feature of the Object Model to help us:   Records.BypassLocks().

This neat little method, part of the  Microsoft.Office.RecordsManagement.RecordsRepository namespace, allows use to define a delegate function that be be run even on locked down records.  Wrap it in a security delegate and you have the makings of a class that can be used to provide users (preferably power users and admins) with tools to manage their records centre WITHOUT suspending record handling functions.

Remember - this allows you to override records handling, so only use this if you really need to.  However if you have found this blog and read this far, you probably really need to!

Here is a basic method that shows you how to use Records.BypassLocks() in a simple record cancelling scenario.  In this example I pass in URL to the offending record, open a security delegate (in order to ensure that the process has permission to perform this task), and then use BypassLocks to edit the meta-data on the document to change the name and title and set the Status to "CANCELLED".

Please not that I have present this a method inside a class with no constructor - I will leave that up to you (simple copy the methods into whatever class you are working in if this confuses you).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Utilities;
using Microsoft.Office.RecordsManagement.RecordsRepository;  //You are going to need to include the dll in teh references
using System.Web;

namespace BusinessLayer
{

   public void Cancelrecord(string documentURL)
   {
        // Pass in the document URL in a format such as:
        //string documentURL = "http://test_reocrds/records/wrong_file.pdf";  

        //Run this in a security delegate

        SPSecurity.RunWithElevatedPrivileges(delegate() {

                using (SPSite site = new SPSite(SPContext.Current.Web.Url))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        web.AllowUnsafeUpdates = true;
                        try
                        {

                            SPListItem target = web.GetListItem(documentURL);

                            Records.BypassLocks(target, delegate(SPListItem item)
                            {         //Perform any action on the item. 
                                       CancelItem(item);
                            });

                        }
                        catch (Exception ex)  // it all throws up to here
                        {
                             throw ex;
                        }
                        finally
                        {

                            web.AllowUnsafeUpdates = false;

                        }
                    }
                }
            });
       }

        //Set item name to start with CANCELLED and set status to CANCELLED

        public void CancelItem(SPListItem item)
        {
            try
            {        

                // Update the name of the file

                item.File.CheckOut();
                item.TrySetValue("Name", String.Format("CANCELLED {0}", item.Name));
                item.TrySetValue("Title", String.Format("CANCELLED {0}", item.Title));
                item.TrySetValue("Status", "CANCELLED");  //

                item.Update();

                //This records library is set to use check in/out and versioning so it records any updates such as this

                item.File.CheckIn("CANCELLED", SPCheckinType.MinorCheckIn);

            }
            catch (Exception ex)
            {
                // Permissions failed.  Undo checkout
                item.File.UndoCheckOut();
                throw ex;
            }
        }
    }

}


 In my example above I use an Extensions class to add useful methods to SPListItem class to get or set values.  Here is the class:
using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
namespace BusinessLayer

{

    public static class Extensions

    {

        public static T TryGetValue(this SPListItem listItem, string fieldInternalName)
        {
            if (!String.IsNullOrEmpty(fieldInternalName) &&
                listItem != null &&
                listItem.Fields.ContainsField(fieldInternalName))
            {
                object untypedValue = listItem[fieldInternalName];
                if (untypedValue != null)
                {
                    var value = (T)untypedValue;

                    return value;
                }
            }

            return default(T);
        }

        public static bool TrySetValue(this SPListItem listItem, string fieldInternalName, T value)
        {
            try
            {
                if (!String.IsNullOrEmpty(fieldInternalName) &&
                    listItem != null &&
                    listItem.Fields.ContainsField(fieldInternalName))
                {
                    listItem[fieldInternalName] = value;
                }

                return true;
            }
            catch
            {
                throw;
            }
        }
    }
}








Thursday 9 January 2014

Hiding the Sharepoint 2010 ribbon from users without losing the scrollbar or title bar area when using a list webpart

There are many blog posts suggesting techniques to hide the Sharepoint ribbon using security trimmed controls in the Sharepoint masterpage, however I have found that none of them properly address the issue of how Sharepoint processes user clicks with the ribbon: namely that the titlebar area will disappear if the ribbon has been hidden with CSS!

In my site I use this area to display location specific information taken from a BCS connection, and provide location specific buttons and actions (all using sharepoint delegate controls), so there was no way I could lose this section of the page. I have therefore had to work out how to stop this from happening.


In order to solve this we must first implement the standard method of hiding the ribbon with CSS, and use a security trimmed control in order to decide which users get to access the ribbon and which do not. This has been gone over in detail elsewhere so I will only briefly recap this now.
First, edit the masterpage and set a style element in the "s4-ribbonrow" div to hide it:
<div class="s4-pr s4-ribbonrowhidetitle" id="s4-ribbonrow" style="display: none;"%>
<%--added display:none to hide Ribbon--%>
Next, add a security trimmed control to make the ribbon visible for administrators (in my case this is users with delete permission, hence I have used the "DeleteListItems" PermissionsString:
<%-- Show the ribbon to power users -->
<sharepoint:spsecuritytrimmedcontrol id="RIBBONHIDE_SPSecurityTrimmedControl3" permissionsstring="DeleteListItems" runat="server">
    <script type="text/javascript">
        document.getElementById("s4-ribbonrow").style.display = "block";
    </script>
</sharepoint:spsecuritytrimmedcontrol>

Installing this masterpage and applying it to your site will hide the ribbon from users without delete permission. Hooray!


HOWEVER...
When implementing this techniques you will find a major flaw: if a list webpart is on the page then when a user who has not got the ribbon clicks on the list webpart in a section that would normally cause the ribbon to show, the user will lose the title area at the top of the page. This includes the logo, the breadcrumb trail, and the social icons. After investigation I determined that this is caused by the Ribbon javascript replacing the title area element with the ribbon, and since you have hidden it, this mean that the whole title area is removed. I've followed this through the whole code and it seems to be related to the custom javascript scrolling function that Sharepoint 2010 uses.


The fix

In order to get around this, we have to make changes to a couple of other areas.

First, we must update the following javascript fuction "OnRibbonMinimizedChanged" that is found in the standard Sharepoint INIT.JS javascript file in the 14 hive. DO NOT edit the file directly! It can be simply overriden by including another javascript file later on in the masterpage (see below). To override this function, first create a file called "HideRibbon.js" with the following in it:
var g_spribbon = new Object();
g_spribbon.isMinimized = true;
g_spribbon.isInited = false;
g_spribbon.minimizedHeight = "44px";
g_spribbon.maximizedHeight = "135px";
function OnRibbonMinimizedChanged(ribbonMinimized) {
    ULSxSy: ;
    var ribbonElement = GetCachedElement("s4-ribbonrow");
    var titleElement = GetCachedElement("s4-titlerow");
    if (ribbonElement) {
        ribbonElement.className = ribbonElement.className.replace("s4-ribbonrowhidetitle", "");
        if (titleElement) {
            titleElement.className = titleElement.className.replace("s4-titlerowhidetitle", "");
            if (ribbonMinimized) {
                titleElement.style.display = "block";
            }
            else {
                //titleElement.style.display = "none"; // MODIFICATION -  Commented out - do not hide this element, or it will hide the whole title area when it thinks a ribbon is required
            }
        }
    }
    var wasInited = g_spribbon.isInited;
    g_spribbon.isInited = true;
    var lastState = g_spribbon.isMinimized;
    g_spribbon.isMinimized = ribbonMinimized;
    if (lastState != ribbonMinimized || !wasInited)
        FixRibbonAndWorkspaceDimensions();
}
Upload this file to your site somewhere (in this example I will use the "Shared Documents" list), and add the following to your masterpage in order to including it:
<asp:scriptmanager enablepagemethods="false" enablepartialrendering="true" enablescriptglobalization="false" enablescriptlocalization="true" id="ScriptManager" runat="server">
    <scripts>
        <asp:scriptreference path="<% $SPUrl:~sitecollection/Shared%20Documents/HideRibbon.js%>" runat="server"></asp:scriptreference> 
    </scripts> 
  </asp:scriptmanager>
Put this section after the body element, directly after the form element. I.e. after the following two lines of the masterpage:
<body scroll="no" onload="if (typeof(_spBodyOnLoadWrapper) != 'undefined') _spBodyOnLoadWrapper();" class="v4master">
  <form id="Form1" runat="server" onsubmit="if (typeof(_spFormOnSubmitWrapper) != 'undefined') {return _spFormOnSubmitWrapper();} else {return true;}">

Now change this line in you masterpage:
<div class="s4-pr s4-notdlg s4-titlerowhidetitle " id="s4-titlerow">

Change it to this to remove the class that tells the javascript that this is the bit to hide:
 <div class="s4-pr s4-notdlg " id="s4-titlerow">
<%-- Changed class to remove to s4-titlerowhidetitle so ribbon does not fire up over top of this --%>

Now you should find that the title bar remains active even when the ribbon is active (thus losing some screen real estate), but that restricted users with no ribbon do not lose their title area when they perform an action that would normally have opened the ribbon :)
Best of luck and happy coding!

Tuesday 17 September 2013

Online tools to convert code between VB and C#

Using .Net and the CLR means we have sometimes have to use both VB and C#. If you are anything like me you will forget the more subtle syntactical differences between the two more often than not, a situation I found myself in earlier today when trying to work out how to rewrite an old VB function in C#.

Rather than doing this myself (I very rarely use VB anymore) I decided to try to use some of the resources on the internet.  The following sites will all allow you to convert freely between the two languages by pasting code into a browser control:

CodeTranslator: Code Translation From VB.NET <-> C# - CarlosAG - my favourite - this one managed to convert things the others couldn't.

Convert VB.NET to C# - developerFusion

Convert VB to C# or C# to VB provided by Telerik

Hope this helps!

Saturday 22 June 2013

Changing the name and URL of a document in Sharepoint using c#

Changing a document's name in Sharepoint programatically, particularly in a webpart for example, is not a trivial process.  In theory, we can use the SPListItem.Item("Name") property and update this.  However you may find that non-administrative users can receive  "permission denied" error when they save this back to the database (using a web.Update()).

This is generally because in order to move (rename) a file in Sharepoint, the user must have "delete" permission on list.
Provided the users have enough permission, then the code below works.  The trick here is to use an SPFile object to update the "Name" property, whilst using an SPListItem object to control the files checkin/out status.

Keen readers will see that I am doing a ValidateFormDigest() in order to prevent the "Page Validation" error if this is caused from a form's POST action, but there is also a "web.AllowUnsafeUpdates = true;" command.  I would prefer not to allow unsafe updates and would recommend you keep this line uncommented, but I have left it in in order to give you an option if you find that this still does not work properly in your situation.


 using (SPSite site = new SPSite(SPContext.Current.Web.Url))
 {
     using (SPWeb web = site.OpenWeb())
     {
         try
         {
             SPUtility.ValidateFormDigest();
             SPListItem target = web.GetListItem(fileUrl);  // fileURL should be set to the full path of the document you are changing

             if (target.File.CheckOutType == SPFile.SPCheckOutType.None)
             {
                  //web.AllowUnsafeUpdates = true;         // Shouldn't need this with the validateformDigest
                  try
                  {                      target.File.CheckOut();
                      SPFile filet = web.GetFile(target.Url);
                      filet.Item["Name"] = newName;     // newName is the new name of the file                               
                                            
                      target.Update();                                            
                   }
                   catch (Exception ex)
                   {
                       web.AllowUnsafeUpdates = false;
                       target.File.UndoCheckOut();
                       ShowErrorMessage("Error validating document: " + ex.Message);  // Deal with error
                       return;
                   }
                   target.File.CheckIn("", SPCheckinType.MajorCheckIn);  // Make major version
                                           
                   web.Update();
                   web.AllowUnsafeUpdates = false;

              } 

            catch (Exception ex)
            {
                ShowErrorMessage("Error: " + ex.Message);  //Deal with error
                //throw;
            }
            finally
            {
                web.AllowUnsafeUpdates = false;
            }

        }
    }