Saturday 9 January 2016

Create a custom WCF REST service hosted in SharePoint to interact with external .NET services.

REST is a software architecture that uses uniform resource identifiers (URI) to specify operations against a remote service.


RESTful web services are REST architecture-based web services. In REST architecture, everything is a resource. RESTful web services are lightweight, highly scalable, and maintainable, and they are very commonly used to create APIs for web-based applications.


REST-based (known more commonly as “RESTful”) solutions use standard HTTP GET, POST, PUTand DELETE verbs to perform CRUD operations against a remote source. Support for the standard HTTP verbs provides easy cross-platform data access.

SharePoint 2013 provides a robust Representational State Transfer (REST) interface that allows any technology that supports standard REST capabilities to interact with SharePoint (sites, libraries, lists, etc).

I have one client requirement to show external web service data in SharePoint search; the search should get records from the SharePoint environment as well as external databases. The external database used is updated frequently.

I have made one pictorial representation of the solution design that we are now going to achieve.




To achieve this, I have tried three approaches as
         a) Directly calling .Net external web service using JQuery (not working due to XML response)
         b) Using the BCS service (which works but requires a method to get all records, and DB sync is not facilitated)
         c) Create custom web service and interact with external service.


Step-by-Step Instructions

1. Open Visual Studio (as Administrator), select the "SharePoint 2013: Empty Project" template, and create a new.

2. Unload the project, edit the csproj file, add TokenReplacementFileExtensions below the SandboxedSolution element, and set its value to svc.


               NoteThe SVC token is not provided by default in the SharePoint template.

3. Download the external web service (asmx) wsdl file.

4. Right-click on the References node and select the "Add Service Reference" option.





5. Follow the above snapshots, put the WSDL path, and click enter or the icon next to it. You can also change the web reference name (optional).

6. Right-click on the project and then Add > SharePoint Mapped Folder.

            NoteCreate a folder under ISAPI, as the service will not run directly under it.




7. There is no SVC template in the SharePoint Project template. So add Text File and change the file extension from txt to svc. Paste the code given below.

<%@ ServiceHost Language="C#" Debug="true" Service="EgSpi.Default.IMRSearch.Services.ImrService, $SharePoint.Project.AssemblyFullName$"
    Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>


8. Add the required assembly references by right-clicking References, and on the Reference Manager dialogue, select the required references.


9. Create a new folder called "Services" (give it any name) and add one class and an interface file.

namespace ABC.Default.IMRSearch.Services
{
    [ServiceContract]
    public interface IImrService
    {
        [OperationContract]
        [WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, Method = "POST", UriTemplate = "GetSpecificDocument", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
        List<WebServiceModel> GetSpecificDocument(string sid, string mid, string options, string resultCount, string searchText);
    }
}


namespace ABC.Default.IMRSearch.Services
{
    [BasicHttpBindingServiceMetadataExchangeEndpoint]
    [AspNetCompatibilityRequirementsAttribute(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class ImrService : IImrService
    {
        #region private variables

        private readonly ILogger _logger;

        #endregion private variables

        #region constructor

        public ImrService()
        {
            _logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
        }

        #endregion constructor

        public List<WebServiceModel> GetSpecificDocument(string sid, string mid, string options, string resultCount, string searchText)
        {
            #region trace

            _logger.TraceToDeveloper("Start GetSpecificDocument Method (" + Constants.ASSEMBLY_NAME + " | IMRSearchWP | GetSpecificDocument())",
                LoggerHelper.DefaultEventID, TraceSeverity.Verbose, LoggerHelper.CategoryDefault);

            #endregion trace

            try
            {
                var proxy = new IMRSearchWebReference.EKService
                {
                    Url = "https://kvalitet.imr.no/EKWeb/Login/EKService.asmx",
                    Credentials = CredentialCache.DefaultCredentials
                };

                XmlNode xmlResponse = proxy.GetSearchData(sid, mid, options, searchText);

                List<WebServiceModel> getDocumentList = new List<WebServiceModel>();
                if (!ReferenceEquals(xmlResponse, null))
                {
                    var xmlNodeList = xmlResponse.SelectNodes(Constants.NodeToSelect);
                    if (!ReferenceEquals(xmlNodeList, null))
                    {
                        getDocumentList = (from XmlElement report in xmlNodeList
                                           select new WebServiceModel
                                           {
                                               Id = report.GetAttribute(Constants.Id),
                                               Title = report.GetAttribute(Constants.Title),
                                               Notes = report.GetAttribute(Constants.Notes)
                                           }
                                          ).Take(Convert.ToInt32(resultCount)).ToList();

                        if (getDocumentList.Count < 1)
                        {
                            return null;
                        }
                    }
                }
                return getDocumentList;

                #region trace

                _logger.TraceToDeveloper("Stop GetSpecificDocument Method (" + Constants.ASSEMBLY_NAME + " | IMRSearchWP | GetSpecificDocument())",
                    LoggerHelper.DefaultEventID, TraceSeverity.Verbose, LoggerHelper.CategoryDefault);

                #endregion trace
            }
            catch (Exception ex)
            {
                _logger.LogToOperations(ex, LoggerHelper.DefaultEventID, EventSeverity.Error, LoggerHelper.CategoryDefault);
                return null;
            }
        }
    }
}

10. Now deploy the solution, check it by opening inetmgr, and it should be placed under the site URL mapped to the folder structure created under ISAPI.

            Note: Click and check Package.package file there your svc file should present.



11. Now the service is created and hosted in SharePoint. Create a client to consume the REST SVC web service.

12. Right-click Add > New Item > Visual Web Part. Add a jquery reference, one textbox, a button, and the required hidden fields (see source code).

13. Paste the code on .cs file on the page load method.

protected void Page_Load(object sender, EventArgs e)
        {
            #region trace

            _logger.TraceToDeveloper("Start loading the IMRSearchWP (" + Constants.ASSEMBLY_NAME + " | IMRSearchWP | PageLoad())",
                LoggerHelper.DefaultEventID, TraceSeverity.Verbose, LoggerHelper.CategoryDefault);

            #endregion trace

            try
            {
                string divDisplayVal = Convert.ToString(Display);
                searchDiv.Style.Add(Constants.DisplayProp,
                    divDisplayVal.ToLower().Equals(Constants.DisplayValue.ToLower())
                    ? Constants.DisplayNone
                    : Constants.DisplayBlock);

                hddnSid.Value = SID;
                hddnMid.Value = MID;
                hddnOption.Value = Options;
                hddnResultCount.Value = ResultCount;
                hddnNoResultMessage.Value = NoResultMessage;

                #region "trace"

                _logger.TraceToDeveloper("Finish loading the IMRSearchWP (" + Constants.ASSEMBLY_NAME + " | IMRSearchWP | PageLoad())",
                    LoggerHelper.DefaultEventID, TraceSeverity.Verbose, LoggerHelper.CategoryDefault);

                #endregion "trace"
            }
            catch (Exception ex)
            {
                _logger.LogToOperations(ex, LoggerHelper.DefaultEventID, EventSeverity.Error, LoggerHelper.CategoryDefault);
            }

        }

14. I have also created Web Part properties to pass parameters and customize them dynamically, like
                 a) SID, MID & Options
                 b) Number records to display
                 c) Custom message when no records are present

#region Custom Webpart Properties

        [WebBrowsable(true),
        WebDisplayName("SID"),
        WebDescription("Enter SID value"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public string SID { get; set; }

        [WebBrowsable(true),
        WebDisplayName("MID"),
        WebDescription("Enter MID value"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public string MID { get; set; }

        [WebBrowsable(true),
        WebDisplayName("Options"),
        WebDescription("Enter option value"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public string Options { get; set; }

        [WebBrowsable(true),
        WebDisplayName("Display"),
        WebDescription("Set display value"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public bool Display { get; set; }

        [WebBrowsable(true),
        WebDisplayName("Results Count"),
        WebDescription("Set result count to display"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public string ResultCount { get; set; }

        [WebBrowsable(true),
        WebDisplayName("No Result Message"),
        WebDescription("Enter message for no results"),
        Personalizable(PersonalizationScope.Shared),
        Category("IMR Search")]
        public string NoResultMessage { get; set; }

 #endregion Custom Webpart Properties


15. Use the JavaScript code below to get data from the custom service.

<script type="text/javascript">
    function callImrWebService(currVal) {
        $("#resultsPanel").empty();
        var sid = $("#hddnSid").val(),
            mid = $("#hddnMid").val(),
            options = $("#hddnOption").val(),
            resultCount = $("#hddnResultCount").val(),
            noResultMessage = $("#hddnNoResultMessage").val(),
            searchText = $("#txtSearchBox").val();


        var serviceUri = _spPageContextInfo.webAbsoluteUrl + "/_vti_bin/EgSpi/Default/Search/ImrSearch.svc/GetSpecificDocument";
        $.ajax({
            url: serviceUri,
            data: '{"sid": "' + sid + '", "mid": "' + mid + '", "options": "' + options + '", "resultCount": "' + resultCount + '", "searchText": "' + searchText + '"}',
            type: "POST",
            contentType: "application/json",
            dataType: "json",
            success: function (data) {
                if (data.GetSpecificDocumentResult == null) {
                    $("#resultsPanel").html(noResultMessage);
                } else {
                    showPresidentsList(data.GetSpecificDocumentResult);
                }
            },
            error: function (err) {
                alert("Error: " + JSON.stringify(err));
            }
        });
    }
function showDocumentsList(documentData) {
        $.each(documentData, function () {
            var aHref = "https://abc.com/EKWeb/GetDoc.aspx?id=" + $(this)[0].Id;
            var string = " <div class='outer'><div class='main'><div ><strong class='ms-srch-item-highlightedText'><a href='" + aHref + "'>" + $(this)[0].Title + "</a></strong></div></div> <div class='notes'><p>" + $(this)[0].Notes + "</p></div><div><a style='color:#338200' href='" + aHref + "'>" + aHref + "</a></div></div>";
            $('#resultsPanel').append(string);
        });
    }
</script>

16. The final output will come like this using CSS (refer to source code).


This can be used simply as a standalone web part to get records from an external web service.

But my objective is to use the above functionality with the out-of-the-box search box provided on the page and on the master page as well.

Add the WP on the page itself and also on the page where the master page search box will redirect you.

So, use the latest source code provided to achieve this. Edit the page and place your custom web part below the SharePoint Search Box and SharePoint Search Results WebPart.

If you face any issue, do write a comment or mail me.

Download source code (click here..)


🚀 "Happy Coding" 🚀