How to override Web.UI.Page - Events in Sharepoint? - 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$" %>

Related

How do I Response.Write from a Sandboxed Solution?

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

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) {
}
}

Is it possible to extract text by page for word/pdf files using Apache Tika?

All the documentation I can find seems to suggest I can only extract the entire file's content. But I need to extract pages individually. Do I need to write my own parser for that? Is there some obvious method that I am missing?
Actually Tika does handle pages (at least in pdf) by sending elements <div><p> before page starts and </p></div> after page ends. You can easily setup page count in your handler using this (just counting pages using only <p>):
public abstract class MyContentHandler implements ContentHandler {
private String pageTag = "p";
protected int pageNumber = 0;
...
#Override
public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException {
if (pageTag.equals(qName)) {
startPage();
}
}
#Override
public void endElement (String uri, String localName, String qName) throws SAXException {
if (pageTag.equals(qName)) {
endPage();
}
}
protected void startPage() throws SAXException {
pageNumber++;
}
protected void endPage() throws SAXException {
return;
}
...
}
When doing this with pdf you may run into the problem when parser doesn't send text lines in proper order - see Extracting text from PDF files with Apache Tika 0.9 (and PDFBox under the hood) on how to handle this.
You'll need to work with the underlying libraries - Tika doesn't do anything at the page level.
For PDF files, PDFBox should be able to give you some page stuff. For Word, HWPF and XWPF from Apache POI don't really do page level things - the page breaks aren't stored in the file, but instead need to be calculated on the fly based on the text + fonts + page size...
You can get the number of pages in a Pdf using the metadata object's xmpTPg:NPages key as in the following:
Parser parser = new AutoDetectParser();
Metadata metadata = new Metadata();
ParseContext parseContext = new ParseContext();
parser.parse(fis, handler, metadata, parseContext);
metadata.get("xmpTPg:NPages");

How To Make UserControl In MasterPage Public For All Child Pages

I have a UserControl which I have added to my web.config
<add tagPrefix="BCF" src="~/controls/MyMessageBox.ascx" tagName="error"/>
and added to my master page
<BCF:error ID="BCError" runat="server" Visible="false" />
Now I need to be able to reference this control AND its public properties from all child pages that use that masterpage. I did this is my BasePage OnLoad event
public UserControl BCError;
BCError = (UserControl)Master.FindControl("BCError");
Problem is, although I can do this in the .aspx page
BCError.Visible = true;
I cannot reference any of the Controls properties I have put in? Such as ShowError .. If I do
BCError.ShowError = "Error Message";
I just get an error saying
'System.Web.UI.UserControl' does not contain a definition for 'ShowInfo' and no extension method 'ShowInfo'
Can you please point me in the right direction!
This is the code for the user control... I can use the properties in the masterpage code behind (And in a page if I put the control directly into it) but cannot use them in the child page code behind?? It doesn't even show the properties or wrapper methods in the intellisense?
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
public partial class MyMessageBox : System.Web.UI.UserControl
{
#region Properties
public bool ShowCloseButton { get; set; }
#endregion
#region Load
protected void Page_Load(object sender, EventArgs e)
{
if (ShowCloseButton)
CloseButton.Attributes.Add("onclick", "document.getElementById('" + MessageBox.ClientID + "').style.display = 'none'");
}
#endregion
#region Wrapper methods
public void ShowError(string message)
{
Show(MessageType.Error, message);
}
public void ShowInfo(string message)
{
Show(MessageType.Info, message);
}
public void ShowSuccess(string message)
{
Show(MessageType.Success, message);
}
public void ShowWarning(string message)
{
Show(MessageType.Warning, message);
}
#endregion
#region Show control
public void Show(MessageType messageType, string message)
{
CloseButton.Visible = ShowCloseButton;
litMessage.Text = message;
MessageBox.CssClass = messageType.ToString().ToLower();
this.Visible = true;
}
#endregion
#region Enum
public enum MessageType
{
Error = 1,
Info = 2,
Success = 3,
Warning = 4
}
#endregion
}
Ok I think I reproduced roughly what your describing and I deleted my original answer cause it was way off.
What I found is that when you want a content page to reference a user control being used in a master page and the control is accesible and what not, you will get an error indicating that you need to reference a specific assembly, and then you get errors indicating that no Method exists of type such and such.
By adding the Register page directive on the child page to the user control resolved this issue. I reproduced this even with the control defined in the web.config or on the page. In both cases I still had to explicitly add a Register on the content page.
This doesn't make sense to me but it allowed my code to compile and work. Give it a shot let me know.
Once you do this you can reference the control like
this.Master.MessageBox.ShowInfo();
This assumes that you have a public property called MessageBox on the Master Page.
Edit
I've also found that this works much better if you register the control on both the master and the content page and not use the web.config.
Edit
If you don't want your child page to reference the user control your other option is expose methods on the master page like ShowInfo() which would delegate to the user control.
You need to declare it as your control type to access it's properties.
public MyMessageBox BCError;
BCError = (MyMessageBox )Master.FindControl("BCError");
Try using this in the pages that inherit from your master page:
<%# MasterType VirtualPath="~/MasterPageName.Master" %>

Possible to load a web part inside another?

So, this is what we want to do: We want to have a generic web part with a custom frame around it and then dynamically load other web parts (frameless) inside it. Would this at all be possible you think? A bit like Jan Tielens SmartPart, only not for ASP.Net User Controls, but for other Web parts... ;)
Edit: We've been able to do this now. The solution was actually pretty simple. Check out the code:
public class WebPartWrapper : System.Web.UI.WebControls.WebParts.WebPart {
protected override void CreateChildControls() {
Panel pnl = new Panel();
this.Controls.Add(pnl);
WebPart dynamicPart = WebPartFactory.CreateWebPart("RSSViewer");
pnl.Controls.Add(dynamicPart);
}
}
Easy as that... We also use reflection to store the webparts as Xml etc., but that's beside the point.
I don't think so. I tried this a while back and it complained about only being able to add WebPartZone items in Page Init. I think by the time it get's to initialising your "container" WebPart it's too late to add more zones as the holding page has already been initialised.
public class WebPartWrapper : System.Web.UI.WebControls.WebParts.WebPart {
protected override void CreateChildControls() {
Panel pnl = new Panel();
this.Controls.Add(pnl);
var factory = new WebPartFactory()
WebPart dynamicPart = factory.CreateWebPart("RSSViewer", this.Guid);
pnl.Controls.Add(dynamicPart);
}
}
public class WebPartFactory {
public WebPart CreateWebpart(string webpartName, Guid parentWebPartGuid)
{
var config = ConfigurationFactory.LoadConfiguration(webpartName);
Assembly webPartAssembly = Assembly.Load(config.Assembly);
Type webPartType = webPartAssembly.GetType(config.Class);
object actualWebPart = Activator.CreateInstance(webPartType);
foreach (var item in config.Properties)
{
PropertyInfo webPartProperty = webPartType.GetProperty(item.Name);
object webPartPropertyValue = Convert.ChangeType(itemValue, Type.GetType(item.Type));
if (!String.IsNullOrEmpty(item.Value))
webPartProperty.SetValue(actualWebPart, webPartPropertyValue, null);
}
RunMethod("set_StorageKeyInternal", actualWebPart, new object[] { parentWebPartGuid });
return actualWebPart as WebPart;
}
private void RunMethod(string methodName, object objectInstance, object[] methodParameters)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic;
Type t = objectInstance.GetType();
MethodInfo m = GetMethod(t, methodName, flags);
if (m != null)
{
m.Invoke(objectInstance, methodParameters);
}
}
private MethodInfo GetMethod(Type instanceType, string methodName, BindingFlags flags)
{
MethodInfo m = instanceType.GetMethod(methodName, flags);
if (m != null)
{
return m;
}
if (instanceType.GetType() == typeof(object) || instanceType.BaseType == null)
{
return null;
}
return GetMethod(instanceType.BaseType, methodName, flags);
}
}
This code needs some explaining... Please excuse me if it does not compile, I had to remove a fair bit of the original code, it was very implementation specific stuff. I've not shown the "config" class either, it's just a container for configuration of webparts, just a bunch of properties. There are 2 issues I'd like to discuss in more detail:
parentWebPartGuid - This is the Guid (UniqueId?) of the hosting webpart. For some reason we have to set "StorageKeyInternal" to this value, using reflection (it's a private property). You can possibly get away with not setting it, but at least for the majority of webparts we had to set it.
config.Properties - This is the config values (we set them in a custom .xml file, but feel free to get this from anywhere). It can look a little like this..
In our framework we also support stuff like dynamic property values etc., but that's for another day... Hope this all makes sense and can help somebody.
There are (at least) two ways to do this: using iframe HTML element, or just a div whose content is changed by JavaScript (probably with Ajax).
[NOTE] My answer is generic (ie. on Web design side), I have no idea how it in your technical context, so maybe I should delete this answer...
No chance on getting the source for the WebPartFactory class is there? Or maybe a bit more information about it? Pseudo code maybe? If a custom web part is in the gallery it could be referenced in the same way as RSSViewer is correct? I'm just not really sure how to go about doing what you have done here, and I would very much like to better understand how to do this.
Thanks!
When a want to instantiate a custom webpart inside another custom webpart i use the following code in the .ascx
<%# Register tagPrefix="uc1" Namespace="Megawork.Votorantim.Intranet.Webparts_Intranet.LikeButton" Assembly="Megawork.Votorantim.Intranet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=769156d154035602" %>
The Namespace value and the Assembly value can be copied from the SafeControls line from the webconfig or from the package file (in manifest tab) :)
When i want to instantiate it dinammicaly (in fact) is use the following code in the .cs
//This is the namespace of the control that will be instantiated dinamically
string type = "My.Custom.Namespace.WebpartToBeAdded.WebpartToBeAdded";
// Instantiate the control dinamically based on his type
System.Web.UI.WebControls.WebParts.WebPart genericWP = (System.Web.UI.WebControls.WebParts.WebPart)Activator.CreateInstance(Type.GetType(type));
// sets the page to the genericWP (i dont know if this is required)
genericWP.Page = this.Page;
// Note: if you want to call custom methods of the dinamically instantiated controls (like a custom load method) you will need to create an interface and make your dinamically instantiated webpart implement it. You will need to do it in that file that have the following code: private const string _ascxPath #"~/_CONTROLTEMPLATES/...". Then you can do the following
//IMyInterface ig = (IMyInterface)genericWP;
//ig.MyCustomLoadMethod(someParam);
// Adds the controls to a container, an asp panel by example.
panelDinamicControls.Controls.Add(genericWP);

Resources