How do I Response.Write from a Sandboxed Solution? - sharepoint

I have a requirement to create an HTML table and shoot it out to the user as a spreadsheet. I've done this many times in plain old ASP.NET, but it's kicking my behind in SharePoint. It's a simple Button with an event handler that calls 3 files stored in a Module (accessible to a sandboxed solution) and then tries to shove the text out to the user along with the appropriate content-type and content-disposition. When I get this part working, the SampleData.txt portion will be replaced with data from multiple lists. Unfortunately, the page does it's postback, but no file is offered to the user for opening or download.
I've read in a few places that Response.Write is not usable in web parts, but surely there is some other way of accomplishing this task...? Here's what I want to do - is there a usable alternative?
protected override void CreateChildControls()
{
Button ReportButton = new Button();
ReportButton.Text = "Generate DCPDS Report";
ReportButton.Click += new EventHandler(ReportButton_Click);
this.Controls.Add(ReportButton);
}
protected void ReportButton_Click(Object sender, EventArgs e)
{
string header = "";
string sampleData = "";
string footer = "";
using (SPWeb webRoot = SPContext.Current.Site.RootWeb)
{
header = webRoot.GetFileAsString("SpreadsheetParts/Header.txt");
sampleData = webRoot.GetFileAsString("SpreadsheetParts/SampleData.txt");
footer = webRoot.GetFileAsString("SpreadsheetParts/Footer.txt");
}
StringBuilder sb = new StringBuilder();
sb.Append(header);
sb.Append(sampleData);
sb.Append(footer);
Page.Response.Clear();
HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=June2010Spreadsheet.xls");
HttpContext.Current.Response.Write(sb.ToString());
//HttpContext.Current.Response.End();
}

I have not tested this, and this would make an interesting Proof of Concept, but I don't have any virgin SharePoint 2010 servers ready for testing, so I cannot test this for you. But just as a thought, could you not place the contents of your excel sheet in a hidden field, and do something like:
HTML:
<script type="text/javascript">
function downloadExcel() {
location.href='data:application/download,' + encodeURIComponent(
document.getElementById('<%= hfExcelData.ClientID %>').value
);
}
</script>
C#:
protected override void CreateChildControls()
{
Button ReportButton = new Button();
ReportButton.Text = "Generate DCPDS Report";
ReportButton.Click += new EventHandler(ReportButton_Click);
this.Controls.Add(ReportButton);
}
protected void ReportButton_Click(Object sender, EventArgs e)
{
string header = "";
string sampleData = "";
string footer = "";
using (SPWeb webRoot = SPContext.Current.Site.RootWeb)
{
header = webRoot.GetFileAsString("SpreadsheetParts/Header.txt");
sampleData = webRoot.GetFileAsString("SpreadsheetParts/SampleData.txt");
footer = webRoot.GetFileAsString("SpreadsheetParts/Footer.txt");
}
StringBuilder sb = new StringBuilder();
sb.Append(header);
sb.Append(sampleData);
sb.Append(footer);
this.hfExcelData.value = sb.ToString();
Page.ClientScript.RegisterStartupScript(this.GetType(), "ExcelUploader", "downloadExcel();", true)
}

I'm struggling with similar. My understanding is that in the sandbox, SP is actually proxying calls to your page, getting the response, and then returning it's own response to the client. That proxy doesn't allow all functions of HttpContext or the SP classes. In my testing, ContentType and headers are not returned. SP (in sandboxed code) will always return text/html and ignore anything else from your webpart.
One way to get the HTML part of what you want is to use an asp:literal. You could follow these steps to create an application page with a webpart on it. Remove the master page reference and the placeholders, so that the only thing returned is your webpart content. In the webpart, add a literal and set the text to the HTML table. At this point, you would have a page that just returns the table as HTML. Here is another post outlining the exact technique. However, you still would not be able to change the content type or add headers. Maybe combined with the first answer that would get you a solution though?

use the default javascript function on a button or linkbutton
STSNavigate(dUrl);
calling the page that creates the HttpContext.Current.Response

Related

How to override Web.UI.Page - Events in Sharepoint?

I want to compress the viewstate. Therefore I need to override SavePageStateToPersistenceMedium wich belongs to Web.UI.Page. In "normal" ASP.Net thats quite easy but in my sharepoint-project I cannot find any place where I have a class that is inherited from Web.UI.Page
My PageLayouts have no code behind, neither has the masterPage.
The best solution would be for me to be able to handle that in a pageLayout, because I do not want every Page to cache the ViewState.
To make it a bit clearer. This is the code I want to put "somewhere":
public abstract class BasePage : System.Web.UI.Page
{
private ObjectStateFormatter _formatter =
new ObjectStateFormatter();
protected override void
SavePageStateToPersistenceMedium(object viewState)
{
MemoryStream ms = new MemoryStream();
_formatter.Serialize(ms, viewState);
byte[] viewStateArray = ms.ToArray();
ClientScript.RegisterHiddenField("__COMPRESSEDVIEWSTATE",
Convert.ToBase64String(
CompressViewState.Compress(viewStateArray)));
}
protected override object
LoadPageStateFromPersistenceMedium()
{
string vsString = Request.Form["__COMPRESSEDVIEWSTATE"];
byte[] bytes = Convert.FromBase64String(vsString);
bytes = CompressViewState.Decompress(bytes);
return _formatter.Deserialize(
Convert.ToBase64String(bytes));
}
}
I would inherit from PublishingLayoutPage (which in turns way back inherits from Page) instead and let all of my page-layouts use this base page as codebehind.
This means you need to alter you page-layouts' page directive like so:
<%# Page Language="C#" Inherits="YourNameSpace.BasePage, $SharePoint.Project.AssemblyFullName$" %>

How can I remove the tables rendered around the webparts in the Rich Content area?

How would I override the tables rendered around the webparts in the "Rich Content" area?
I have successfully removed the tables around webpartzones and their webparts but can't figure how to remove the tables around Rich Content area webparts.
I am not using the Content Editor WebPart.
The "Rich Content" area I am using is created using the PublishingWebControls:RichHtmlField.
This is the control which has content and webparts.
Bounty here.
I have pondered this myself in the past and I've come up with two options, though none are very appealing, so have not implemented them:
Create a custom rich text field. Override render, call base.Render using a TextWriter object and place the resulting html in a variable, which you then "manually" clean up, before writing to output.
Create a custom rich text field. Override render, but instead of calling base.Render, take care of the magic of inserting the webparts yourself. (This is probably trickier.)
Good luck!
Update, some example code I use to minimize the output of the RichHtmlField:
public class SlimRichHtmlField : RichHtmlField
{
protected override void Render(HtmlTextWriter output)
{
if (IsEdit() == false)
{
//This will remove the label which precedes the bodytext which identifies what
//element this is. This is also identified using the aria-labelledby attribute
//used by for example screen readers. In our application, this is not needed.
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
HtmlTextWriter htw = new HtmlTextWriter(sw);
base.Render(htw);
htw.Flush();
string replaceHtml = GetReplaceHtml();
string replaceHtmlAttr = GetReplaceHtmlAttr();
sb.Replace(replaceHtml, string.Empty).Replace(replaceHtmlAttr, string.Empty);
output.Write(sb.ToString());
}
else
{
base.Render(output);
}
}
private string GetReplaceHtmlAttr()
{
return " aria-labelledby=\"" + this.ClientID + "_label\"";
}
private string GetReplaceHtml()
{
var sb = new StringBuilder();
sb.Append("<div id=\"" + this.ClientID + "_label\" style='display:none'>");
if (this.Field != null)
{
sb.Append(SPHttpUtility.HtmlEncode(this.Field.Title));
}
else
{
sb.Append(SPHttpUtility.HtmlEncode(SPResource.GetString("RTELabel", new object[0])));
}
sb.Append("</div>");
return sb.ToString();
}
private bool IsEdit()
{
return SPContext.Current.FormContext.FormMode == SPControlMode.Edit || SPContext.Current.FormContext.FormMode == SPControlMode.New;
}
}
This code is then used by your pagelayout like this:
<YourPrefix:SlimRichHtmlField ID="RichHtmlField1" HasInitialFocus="false" MinimumEditHeight="200px" FieldName="PublishingPageContent" runat="server" />
Got it:
https://sharepoint.stackexchange.com/a/32957/7442

Single page design using Orchard CMS

I have a client who want's a single page design for his site where the content for each "page" is shown/hidden using javascript as the user navigates the site.
I'm not sure on the best way to approach this using Orchard. One option would be to have the content all on a single page content item but then you lose the ability to use the navigation features of Orchard and can't let the client think about administration in terms of pages.
Does anyone have ideas or experiences on how best to set this up in Orchard CMS?
Here's the solution I used based on Bertrand's advice:
public ActionResult Display(int id)
{
var contentItem = _contentManager.Get(id, VersionOptions.Published);
dynamic model = _contentManager.BuildDisplay(contentItem);
var ctx = _workContextAccessor.GetContext();
ctx.Layout.Metadata.Alternates.Add("Layout_Null");
return new ShapeResult(this, model);
}
I created a new module with a controller containing the action method above. The action method takes a parameter for the content part id. The _contentManager and _workContextAccessor objects are being injected into the controller. The Layout.Null.cshtml view was created exactly like Bertrand suggested.
Here's what I would do to achieve that sort of very polished experience without sacrificing SEO, client performance and maintainability: still create the site "classically" as a set of pages, blog posts, etc., with their own URLs. It's the home page layout that should then be different and bring the contents of those other pages using Ajax calls.
One method that I've been using to display the same contents as a regular content item, but from an Ajax call (so without the chrome around the content, without bringing the stylesheet in, as it's already there, etc.) is to have a separate controller action that returns the contents in a "null layout":
var ctx = _workContextAccessor.GetContext();
ctx.Layout.Metadata.Alternates.Add("Layout_Null");
return new ShapeResult(this, shape);
Then, I have a Layout.Null.cshtml file in my views that looks like this:
#{
Model.Metadata.Wrappers.Clear();
}
#Display(Model.Content)
Clearing the wrappers removes the rendering from document.cshtml, and the template itself is only rendering one zone, Content. So what gets rendered is just the contents and nothing else. Ideal to inject from an ajax call.
Does this help?
Following along the lines of Bertrand's solution, would it make more sense to implement this as a FilterProvider/IResultFilter? This way we don't have to handle the content retrieval logic. The example that Bertrand provided doesn't seem to work for List content items.
I've got something like this in my module that seems to work:
public class LayoutFilter : FilterProvider, IResultFilter {
private readonly IWorkContextAccessor _wca;
public LayoutFilter(IWorkContextAccessor wca) {
_wca = wca;
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
var workContext = _wca.GetContext();
var routeValues = filterContext.RouteData.Values;
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) {
workContext.Layout.Metadata.Alternates.Add("Layout_Null");
}
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
}
}
Reusing Rahul's answer with added code to answer #tuanvt's question. I'm honestly not sure what your question is but if seems like you want to access the data sent with the ajax request. If it's JSON you're sending set contentType: "application/json" on the request, JSON.stringify() it , then access it in Rahul's proposed ActionFilter by extracting it from the request stream. Hope it helps in any way.
public class LayoutFilter : FilterProvider, IResultFilter {
private readonly IWorkContextAccessor _wca;
public LayoutFilter(IWorkContextAccessor wca) {
_wca = wca;
}
public void OnResultExecuting(ResultExecutingContext filterContext) {
var workContext = _wca.GetContext();
var routeValues = filterContext.RouteData.Values;
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) {
workContext.Layout.Metadata.Alternates.Add("Layout_Null");
if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("application/json"))
{
var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
filterContext.HttpContext.Request.InputStream.Position = 0;
var json = Encoding.UTF8.GetString(bytes);
var jsonObject = JObject.Parse(json);
// access jsonObject data from ajax request
}
}
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
}
}

Creating Document set programatically in sharepoint 2010

I am creating a document set programatically on buttom click event
public void btnCreateDocumentSet_Click(object sender, EventArgs e)
{
try
{
lblError.Text = string.Empty;
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (web = SPControl.GetContextSite(Context).RootWeb)
{
web.AllowUnsafeUpdates = true;
String url = web.Lists[" Tracker"].RootFolder.ServerRelativeUrl.ToString();
SPList list = web.Lists["Tracker"];
Hashtable props = new Hashtable();
props.Add("Number", "item1");
props.Add("Type", "item2");
DocumentSet ds = DocumentSet.Create(list.RootFolder, "NewDocumentSet3", web.ContentTypes["MydocumentSet2"].Id, props, true);
//test
//web.Dispose();
}
}
);
}
catch (SPException ex)
{
lblError.Text = ex.Message;
}
}
I am not getting any exceptions.On button click i am redirected to an error like the following
However the document set named NewDocumentSet3 is created in document library but it looks like a folder(the icon i mean) . when i go to document library->documents tab->New Document I am not getting the document set type .Kindly advise me on this issue.
Thanks in advance
First of all, turn off custom errors, like the screenshots tells you.
Then replace your catch of SPException with a catch of all Exceptions.
Even better yet, test code like this in a separate console app, not right inside handlers.
Look up some resources on the internet on how to debug SharePoint applications. A breakpoint will get you a long way in this particluar situation.
I am very wary of your list called "[space]Tracker". Looks suspicious to me.
Try adding
props.Add("HTML_x0020_File_x0020_Type", "SharePoint.DocumentSet");
to the properties hashset that gest passed in to DocumentSet.Create method.

Outlook add in , text box , delete\backspace not working

I developed an outlook add in (custom task pane), with web browser in the user control.
All the things working well beside the backspace or the delete button when I am writing something in text box in the web browser, I can't use those keys, am I missing something?
I am a few years late to the party but I managed to fix this. The easiest way to fix this is to ensure proper focus is given to the input fields, so you will need to be able to run your own javascript on whatever page is being loaded.
The javascript I run on the page is as follows (using jQuery):
$(document).on("click", function (e) {
// first let the add-in give focus to our CustomTaskPane
window.external.focus();
// then in our web browser give focus to whatever element was clicked on
$(e.target).focus();
});
the window.external variable contains code run from the plugin (c# or VB I assume) which is exposed so we can interact from web page back to the add-in.
In the add-in code for the custom taskpane set the context of window.external:
// event when webBrowser is finished loading document
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
// sets context of window.external to functions defined on this context
webBrowser1.ObjectForScripting = this;
}
And a public method for focusing:
// can be called by the web browser as window.external.focus()
public void focus()
{
this.Focus();
}
This worked for me, and I hope it helps others. Although do note that this probably doesn't work if the user keyboard navigates using tab, but you can either extend this code for that use case, or safely assume that the average outlook user will have his hand glued to the mouse.
Ok I solved the problem ,
The problem is that the custom task pane in not always gets fucos from the outlook.
So, I raised an event every time that there is "onclick" for all the pane, and then forced the pane to be in focus.
spent a lot of time trying to get this working in Outlook v16.0.13801.20288 the above did not work for me. I ended up with this working code.
Create a user control and add your webbrowser control to it then customize the .cs as below
private void CreateTaskPane() {
MyWinFormUserControl webBrowser = new MyWinFormUserControl();
webBrowser.webBrowser3.Url = new Uri("https://google.com");
webBrowser.webBrowser3.Width = 500;
webBrowser.webBrowser3.Dock = DockStyle.Fill;
webBrowser.webBrowser3.Visible = true;
webBrowser.Width = 500;
webBrowser.Dock = DockStyle.Fill;
webBrowser.Visible = true;
this.CRMTaskPaneControl = CustomTaskPanes.Add(webBrowser, "My App");
//Components.WebViewContainerWPFUserControl webView = (Components.WebViewContainerWPFUserControl)_eh.Child;
//webView.webview.Source = new Uri("https://localhost:3000");
this.CRMTaskPaneControl.Width = 500;
System.Windows.Forms.Application.DoEvents();
this.CRMTaskPaneControl.Control.Focus();
this.CRMTaskPane.Visible = true;
}
public partial class MyWinFormUserControl : UserControl
{
public WebBrowser webBrowser3;
public System.Windows.Forms.WebBrowser webBrowser1;
public MyWinFormUserControl()
{
InitializeComponent();
}
private void InitializeComponent()
{
this.webBrowser3 = new System.Windows.Forms.WebBrowser();
this.SuspendLayout();
//
// webBrowser3
//
this.webBrowser3.Dock = System.Windows.Forms.DockStyle.Fill;
this.webBrowser3.Location = new System.Drawing.Point(0, 0);
this.webBrowser3.MinimumSize = new System.Drawing.Size(20, 20);
this.webBrowser3.Name = "webBrowser3";
this.webBrowser3.Size = new System.Drawing.Size(500, 749);
this.webBrowser3.TabIndex = 0;
this.webBrowser3.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser3_DocumentCompleted);
//
// MyWinFormUserControl
//
this.Controls.Add(this.webBrowser3);
this.Name = "MyWinFormUserControl";
this.Size = new System.Drawing.Size(500, 749);
this.Load += new System.EventHandler(this.MyWinFormUserControl_Load);
this.ResumeLayout(false);
}
void webBrowser3_DocumentCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{
HtmlDocument doc;
doc = webBrowser3.Document;
doc.Click += doc_Click;
}
void doc_Click(object sender, HtmlElementEventArgs e)
{
this.Focus(); // force user control to have the focus
HtmlElement elem = webBrowser3.Document.GetElementFromPoint(e.ClientMousePosition);
elem.Focus(); // then let the clicked control to have focus
}
private void MyWinFormUserControl_Load(object sender, EventArgs e)
{
//Control loaded
}
Turns out this is an easy issue to fix.
Just write
class MyBrowser : WebBrowser {}
Then use MyBrowser instead of the .NET one.

Resources