How to provision Branding files using SharePoint Hosted App in SharePoint Online/Office 365? - sharepoint

I am looking for SharePoint Hosted App Solution which will provision Branding files (JS/CSS/Images) into SharePoint Online/Office 365 environment.
I got a very good article to achive this and tried to implement the same as shown in below link: http://www.sharepointnutsandbolts.com/2013/05/sp2013-host-web-apps-provisioning-files.html
This solution is not working for me and while execution of app, I am getting below error:
Failed to provision file into host web. Error: Unexpected response data from server. Here is the code which is giving me error:
// utility method for uploading files to host web..
uploadFileToHostWebViaCSOM = function (serverRelativeUrl, filename, contents) {
var createInfo = new SP.FileCreationInformation();
createInfo.set_content(new SP.Base64EncodedByteArray());
for (var i = 0; i < contents.length; i++) {
createInfo.get_content().append(contents.charCodeAt(i));
}
createInfo.set_overwrite(true);
createInfo.set_url(filename);
var files = hostWebContext.get_web().getFolderByServerRelativeUrl(serverRelativeUrl).get_files();
hostWebContext.load(files);
files.add(createInfo);
hostWebContext.executeQueryAsync(onProvisionFileSuccess, onProvisionFileFail);
}
Please suggest me, what can be the issue in this code? Or else suggest me another way/reference in which I can Create a SharePoint-Hosted App to provision Branding Files.
Thanks in Advance!

I would use a different method to access host web context as follows:
//first get app context, you will need it.
var currentcontext = new SP.ClientContext.get_current();
//then get host web context
var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);
function getQueryStringParameter(param) {
var params = document.URL.split("?")[1].split("&");
var strParams = "";
for (var i = 0; i < params.length; i = i + 1) {
var singleParam = params[i].split("=");
if (singleParam[0] == param) {
return singleParam[1];
}
}
}
Here are some references:
https://sharepoint.stackexchange.com/questions/122083/sharepoint-2013-app-create-list-in-host-web
https://blog.appliedis.com/2012/12/19/sharepoint-2013-apps-accessing-data-in-the-host-web-in-a-sharepoint-hosted-app/
http://www.mavention.com/blog/sharePoint-app-reading-data-from-host-web
http://www.sharepointnadeem.com/2013/12/sharepoint-2013-apps-access-data-in.html
Additionally, here is an example of how to deploy a master page, however as you might notice during your testing the method used to get host web context is not working as displayed in the video and you should use the one I described before.
https://www.youtube.com/watch?v=wtQKjsjs55I
Finally, here is a an example of how to deploy branding files through a Console Application using CSOM, if you are smart enough you will be able to convert this into JSOM.
https://channel9.msdn.com/Blogs/Office-365-Dev/Applying-Branding-to-SharePoint-Sites-with-an-App-for-SharePoint-Office-365-Developer-Patterns-and-P

Related

Data From REST API In Azure

I have implemented REST API calls using a standalone c# console application. The API returns JSON which i'm deserializing and then storing it in the database.
Now i want to implement the entire logic in Azure platform so that it can invoked by passing start date and an end date and store location (it should run for three location) Below is the code:
static void Main()
{
MakeInventoryRequest();
}
static async void MakeInventoryRequest()
{
using (var client = new HttpClient())
{
var queryString = HttpUtility.ParseQueryString(string.Empty);
// Request headers
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "5051fx6yyy124hhfyuscf34f57ce9");
// Request parameters
queryString["query.locationNumbers"] = "4638";
queryString["availableFromDate"] = "2019-01-01";
queryString["availableToDate"] = "2019-03-07";
var uri = "https://api-test.location.cloud/api/v1/inventory?" + queryString;
using (var request = new HttpRequestMessage(HttpMethod.Get, uri))
using (var response = await client.SendAsync(request))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode == true)
{
List<Inventory> l1 = DeserializeJsonFromStream<List<Inventory>>(stream);
InsertInventoryRecords(l1);
}
if (response.IsSuccessStatusCode == false)
{
throw new Exception("Error Response Code: " + response.StatusCode.ToString() + "Content is: " + response.Content.ReadAsStringAsync().Result.ToString());
}
}
}
}
Please suggest the best possible design using Azure components
With the information in hand I think you have multiple options , you need to find out which works for you the best . You can use Cloud service to host the console app ( you will have to change it to worker role , Visual studio will help you to convert that ) . I am not sure about the load which you are expecting but you can always increase and decrease the instance and these can be deployed to different geographies .
I see that you are persisting the data , if you want to do that you can use many of the SQL offerings . For invoking the REST API you can also azure functions and ADF.
Please feel free to comment if you want any more details on the same.

Can't access to Azure Storage folder using Lucene.Net

We decided to implement a search functionality in our API which is developed in ServiceStack, we decided to use Lucene.Net since we heard it was a great indexer to make searches.
We created a worker role whose job is to create the indexes in a Azure Storage folder, we guided ourselves using Leon Cullen's tutorial. We use the AzureDirectory library specified in that post, so we could use the latest Azure SDK.
Then in our API project we added the references for Lucene.Net and AzureDirectory too, our endpoint ended up looking like this:
public object Post(SearchIndex request)
{
List<Product> products = new List<Product>();
var pageSize = -1;
var totalpages = -1;
int.TryParse(ConfigurationManager.AppSettings["PageSize"], out pageSize);
if (request.Page.Equals(0))
{
request.Page = 1;
}
// Get Azure settings
AzureDirectory azureDirectory ;
try
{
// This is the line where we get the Access denied exception thrown at us
azureDirectory = new AzureDirectory(Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(ConfigurationManager.AppSettings["ConnectionStringAzureSearch"]), "indexsearch");
IndexSearcher searcher;
using (new AutoStopWatch("Creating searcher"))
{
searcher = new IndexSearcher(azureDirectory);
}
using (new AutoStopWatch(string.Format("Search for {0}", request.SearchString)))
{
string[] searchfields = new string[] { "Id", "Name", "Description" };
var hits = searcher.Search(QueryMaker(request.SearchString, searchfields), request.Page * pageSize);
int count = hits.ScoreDocs.Count();
float temp_totalpages = 0;
temp_totalpages = (float)hits.ScoreDocs.Count() / (float)pageSize;
if (temp_totalpages > (int)temp_totalpages)
{
totalpages = (int)temp_totalpages + 1;
}
else
{
totalpages = (int)temp_totalpages;
}
foreach (ScoreDoc match in hits.ScoreDocs)
{
Document doc = searcher.Doc(match.Doc);
int producId = int.Parse(doc.Get("Id"));
Product product = Db.Select<Product>("Id={0}", producId).FirstOrDefault();
products.Add(product);
}
}
return new SearchIndexResult { result = products.Skip((int)((request.Page - 1) * 10)).Take(pageSize).ToList(), PageSize = pageSize, TotalPages = totalpages };
}
catch (Exception e)
{
return new HttpResult(HttpStatusCode.NoContent, "azureDirectory. Parameter: " + request.SearchString + ". e: " + e.Message);
}
}
If we run this locally it works as expected, returning us the results we were expecting. But when we published our API to Azure and tried to access to the search endpoint we received an 403 error message with the message 'Access to the path "D:/AzureDirectory" is denied".
We're confused as to why is trying to access to such folder at all, the name of the folder is wrong and I think it's trying to access a local route, we really don't know why does it work fine locally but once it's deployed to Azure it stops working.
The worker role runs without a problems, but it's the API side that cannot access to the folder in Azure Storage. Are we missing some important step in the configuration? The tutorial we followed wasn't very clear for beginners using Lucene.Net or Azure Storage so we fear we might have missed an important step. We've checked our connection strings and everything seems ok though.
As for reference:
https://github.com/azure-contrib/AzureDirectory/blob/master/AzureDirectory/AzureDirectory.cs
when you do this
azureDirectory = new AzureDirectory(Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(ConfigurationManager.AppSettings["ConnectionStringAzureSearch"]), "indexsearch");
This executes
var cachePath = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "AzureDirectory");
var azureDir = new DirectoryInfo(cachePath);
if (!azureDir.Exists)
azureDir.Create();
var catalogPath = Path.Combine(cachePath, _containerName);
var catalogDir = new DirectoryInfo(catalogPath);
if (!catalogDir.Exists)
catalogDir.Create();
_cacheDirectory = FSDirectory.Open(catalogPath);
So simple solution for you might be to have that directory on site root
DirectoryInfo info = new DirectoryInfo(HostingEnvironment.MapPath("~/"));
azureDirectory = new AzureDirectory(storageAccount, containerName, new SimpleFSDirectory(info), true);
I got it to work.
I just got the latest version of AzureDirectory from GitHub.
Got the latest nuGet packages for Azure Storage etc.
Recreated the index.
In addition to #brykneval answer, I tried his solution but last parameter bool compressBlob = false which he set to true made my local debug fail with 404 exception from AzureDirectory library and when I published to Azure web app, it had exception with message: System.IO.InvalidDataException: Block length does not match with its complement.
I removed last parameter from constructor and everything works like a charm. Hope this helps anyone.

wkhtmltopdf fails on Azure Website

I'm using the https://github.com/codaxy/wkhtmltopdf wrapper to create a pdf from a web page on my website (I pass in an absolute url e.g. http://mywebsite.azurewebsites.net/PageToRender.aspx It works fine in dev and on another shared hosting account but when I deploy to an Azure website it fails and all I get is a ThreadAbortException.
Is it possible to use wkhtmltopdf on azure, and if so, what am I doing wrong?
UPDATE:
This simple example just using Process.Start also doesn't work. It just hangs when run on Azure but works fine on other servers.
string exePath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\wkhtmltopdf.exe");
string htmlPath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\Test.html");
string pdfPath = System.Web.HttpContext.Current.Server.MapPath("\\App_Data\\PdfGenerator\\Test.pdf");
StringBuilder error = new StringBuilder();
using (var process = new Process())
{
using (Stream fs = new FileStream(pdfPath, FileMode.Create))
{
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = string.Format("{0} -", htmlPath);
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.Start();
while (!process.HasExited)
{
process.StandardOutput.BaseStream.CopyTo(fs);
}
process.WaitForExit();
}
}
Check out this SO question regarding a similar issue. This guy seems to have gotten it to work. RotativaPDF is built on top of wkhtmltopdf hence the connection. I am in the process of trying it myself on our Azure site - I will post in the near future with my results.
Azure and Rotativa PDF print for ASP.NET MVC3

Get Sharepoint Site Collection URLs by JQuery

I have a special requirement that I need to get all site collection URLs list from JQuery.Can any one please help on that
I am afraid that you cannot get the list of all site collections in pure JavaScript. I assume that you meant all existing site collections in a web application or in all web applications.
If your page (aspx) runs on the SharePoint server you can put some server-side code on the page "rendering" the list as JavaScript and then just access it. This would be the easiest way; probably using Page.ClientScript.RegisterStartupScript.
If your page (and/or the web application) runs outside the SharePoint farm you would have to create and deploy your web service (asmx) or a handler (ashx) to the SharePoint farm. You would probably respond with the site collection list as JSON content and consume it by jQuery on your page (AJAX).
The functionality is available in the server-side SharePoint API only. You would use SPWebService.ContentService.WebApplications and application.Sites to get the list in any case:
var json = new StringBuilder();
foreach (var site in SPWebService.ContentService.WebApplications.SelectMany(
application => application.Sites))
using (site) {
if (json.Length > 0)
json.Append(',');
json.Append('"').Append(site.Url).Append('"');
}
json.Insert(0, "[").Append("]");
--- Ferda
Try below code.you can get all site collection using client object model
function loadWebs() {
var clientContext = new SP.ClientContext.get_current();
this.webs = clientContext.get_web().get_webs();
clientContext.load(this.webs);
clientContext.executeQueryAsync(Function.createDelegate(this, this.onWebsLoaded), Function.createDelegate(this, this.onQueryFailed));
}
function onQueryFailed(sender, args) {
alert('request failed ' + args.get_message() + '\n' + args.get_stackTrace());
}
function onWebsLoaded(sender, args) {
for (var i = 0; i < this.webs.get_count(); i++) {
alert(this.webs.itemAt(i).get_title());
}
}
ExecuteOrDelayUntilScriptLoaded(loadWebs, "sp.js");

SharePoint 2010 Client Object Model - Kerberos/Claims Authentication

I'm trying to read a value from a list in a remote SharePoint site (different SP Web App). The web apps are set up with Claims Auth, and the client web app SP Managed account is configured with an SPN. I believe Kerberos and claims are set up correctly, but I am unable to reach the remote server, and the request causes an exception: "The remote server returned an error: (401) Unauthorized."
The exception occurs in the line ctx.ExecuteQuery(); but it does not catch the exception in the if (scope.HasException) instead, the exception is caught by the calling code (outside of the using{} block).
When I look at the traffic at the remote server using Wireshark, it doesn't look like the request is even getting to the server; it's almost as if the 401 occurs before the Kerberos ticket is exchanged for the claim.
Here's my code:
using (ClientContext ctx = new ClientContext(contextUrl))
{
CredentialCache cc = new CredentialCache();
cc.Add(new Uri(contextUrl), "Kerberos", CredentialCache.DefaultNetworkCredentials);
ctx.Credentials = cc;
ctx.AuthenticationMode = ClientAuthenticationMode.Default;
ExceptionHandlingScope scope = new ExceptionHandlingScope(ctx);
Web ctxWeb = ctx.Web;
List ctxList;
Microsoft.SharePoint.Client.ListItemCollection listItems;
using (scope.StartScope())
{
using (scope.StartTry())
{
ctxList = ctxWeb.Lists.GetByTitle("Reusable Content");
CamlQuery qry = new CamlQuery();
qry.ViewXml = string.Format(ViewQueryByField, "Title", "Text", SharedContentTitle);
listItems = ctxList.GetItems(qry);
ctx.Load(listItems, items => items.Include(
item => item["Title"],
item => item["ReusableHtml"],
item => item["ReusableText"]));
}
using (scope.StartCatch()) { }
using (scope.StartFinally()) { }
}
ctx.ExecuteQuery();
if (scope.HasException)
{
result = string.Format("Error retrieving content<!-- Error Message: {0} | {1} -->", scope.ErrorMessage, contextUrl);
}
if (listItems.Count == 1)
{
Microsoft.SharePoint.Client.ListItem contentItem = listItems[0];
if (SelectedType == SharedContentType.Html)
{
result = contentItem["ReusableHtml"].ToString();
}
else if (SelectedType == SharedContentType.Text)
{
result = contentItem["ReusableText"].ToString();
}
}
}
I realize the part with the CredentialCache shouldn't be necessary in claims, but every single example I can find is either running in a console app, or in a client side application of some kind; this code is running in the codebehind of a regular ASP.NET UserControl.
Edit: I should probably mention, the code above doesn't even work when the remote URL is the root site collection on the same web app as the calling code (which is in a site collection under /sites/)--in other words, even when the hostname is the same as the calling code.
Any suggestions of what to try next are greatly appreciated!
Mike
Is there a reason why you are not using the standard OM?
You already said this is running in a web part, which means it is in the context of application pool account. Unless you elevate permissions by switching users, it won't authenticate correctly. Maybe try that. But I would not use the client OM when you do have access to the API already.

Resources