I am attempting to write a method that will output the content (i.e. HTML) for any renderings that happen to exist within a specific placeholder. The goal is to pass in a Sitecore.Data.Items.Item and the placeholder key that i'm interested in, and the method should return the rendered content.
The issue with this seems to be that there is no page context established, and therefore calling RenderControl() is throwing a null reference error in the GetCacheKey() method of the Sublayout.
Is anyone aware of a way to render a Sublayout or XSLT rendering programmatically?
Here's what I've got so far:
private string GetPlaceholderContent(Item item, string placeHolder)
{
StringWriter sw = new StringWriter();
using (HtmlTextWriter writer = new HtmlTextWriter(sw))
{
foreach (RenderingReference renderingReference in item.Visualization.GetRenderings(Sitecore.Context.Device, false))
{
if (renderingReference.Placeholder == placeHolder)
{
// This ensures we're only dealing with Sublayouts
if (renderingReference.RenderingItem.InnerItem.IsOfType(Sitecore.TemplateIDs.Sublayout))
{
var control = renderingReference.GetControl();
control.RenderControl(writer); // Throws null reference error in GetCacheKey()
}
}
}
}
return sw.ToString();
}
It is almost 8 years since the question was originally asked, and it turn to be Uniform - rendering any item/placeholder fragment!
Yes, it cuts the item you supply into placeholders/renderings:
The next step is to produce markup (for every possible data source out there):
That content is published into CDN and browser picks which version to load = personalization works!
In other words, the question you've asked turned into a cutting-edge product that can do so much more on top of that!
You could reverse-engineer the Uniform assemblies to see how they actually do that ;)
In my opinion, the best way to programmatically render a Sublayout is to use a repeater, and put a <sc:Sublayout> tag in the <ItemTemplate>.
From there you only have to do one or both of the following:
Set the DataSource property of the <sc:Sublayout> to be the string representation of the desired item's GUID (i.e. the data source for the sublayout, if any)
Set the Path property of the <sc:Sublayout> to be the path to the Sublayout that you wish to render.
The server/sitecore will handle the rest.
Related
We regularly use Word to generate large documents with many internal cross-references. I'm writing a tool to find and add blue underlining to these clickable internal cross-references. (I know I can do this at the Word level, but I need to be able to do this even when I don't have access to the original Word file.)
I am using C# and IText 7. I am able to load and scan the existing PDF for these internal links. But what I can't seem to do is change the links' appearance. After searching, I learned that the PdfAnnotation class is for creating new links and not for altering existing ones. I'm told I need to use Put() to change existing ones. I've tried a number of different approaches, but none of them seem to be working.
var pdfDoc = new PdfDocument(new PdfReader(txt_FileLoaded.Text), new PdfWriter(outfile));
for (int x = pgStart; x <= pgEnd; x++)
{
PdfPage page = pdfDoc.GetPage(x);
var annotations = page.GetAnnotations();
foreach (var a in annotations)
{
if (a.GetSubtype() == PdfName.Link)
{
PdfLinkAnnotation link = (PdfLinkAnnotation)a;
var action = link.GetAction();
if (action != null)
{
if (
(action.Get(PdfName.S) == PdfName.URI) ||
(action.Get(PdfName.S) == PdfName.GoToR) )
{
//Do something with external links if you want
}
else if (
(action.Get(PdfName.S) == PdfName.GoTo) ||
(action.Get(PdfName.S) == PdfName.GoToE))
{
//Do something with internal links
link.Put(PdfName.C, new PdfArray(ColorConstants.BLUE.GetColorValue()));
link.Put(PdfName.Border, new PdfArray(new int[] {0,0,5}));
}
}
}
}
}
pdfDoc.Close();
The new file is correctly written when I call Close(), but no underlining is appearing. Again, I've confirmed that it's indeed finding the links. What specific changes do I need to make via Put() to add these underlines?
Thanks for your time!!
To the best of my knowledge what you are trying to achieve cannot be achieved by purely modifying the annotation objects. It might be possible to try playing with appearance streams but I'm not sure it will work and even if it will it can have some side effects.
Annotations just encode the rectangular area which becomes clickable and the action (or destination) that will be triggered when that area is clicked. It is also possible to configure the border of the annotation but PDF specification does not allow you to do fine-grained tuning and thus you won't be able to just set the bottom border to emulate the underlining.
What we will be doing instead is drawing a line directly in the contents of the page in the hope that the annotation position is accurate enough.
We can get annotation's area with annotation.getRectangle(), and then use PdfCanvas to draw a line with matching coordinates (you could also shift it upwards a bit if needed). The code is in Java, but you will find it very easy to convert to C# since only the method names would start with the capital letters.
PdfPage page = pdfDocument.getPage(i);
// Create canvas where we would draw additional lines
PdfCanvas pageCanvas = new PdfCanvas(page);
for (PdfAnnotation annotation : page.getAnnotations()) {
Rectangle annotationArea = annotation.getRectangle().toRectangle();
// Draw a line at the bottom of the annotation area
pageCanvas.setStrokeColor(ColorConstants.BLUE).
moveTo(annotationArea.getLeft(), annotationArea.getBottom()).
lineTo(annotationArea.getRight(), annotationArea.getBottom()).
stroke();
}
This is how the initial PDF looks like:
This is how the output PDF looks like
I am trying to print out multiple images using a foreach loop in the view and passing in an IEnumerable<Image> through the model.
My problem is that the foreach loop doesn't display the images at all, and I have no idea what I am doing wrong. I've tried this about 16 different ways and I am still not getting anything other than a broken image link or nothing at all.
Will someone please take a look and see if they can figure out what I am doing wrong? It's probably something very simple and I'm just missing it.
Here is my Controller:
public ActionResult UploadCommission(CommissionViewModel model)
{
List<Image> fileNames = new List<Image>();
//loop through multiple files
foreach (HttpPostedFileBase file in model.Files)
{
//get file names
string filename = System.IO.Path.GetFileName(file.FileName);
//save files
file.SaveAs(Server.MapPath("~/Content/" + filename));
string filepathtosave = "~/Content/" + filename;
fileNames.Add(new Image()
{
FilePath = filepathtosave
});
}
model.ImageFilePaths = fileNames;
return RedirectToAction("ViewComm", model);
}
And here's my View that displayed the information:
#foreach (CapstoneProject.Models.Image i in Model.ImageFilePaths)
{
<img src="#Url.Content(i.FilePath)" />
}
#Model.Title
#Model.Price
#Model.Limit
#foreach (string tag in Model.Tags)
{
<p>#tag</p>
}
#Model.Description
If you need more information, please let me know.
You are using RedirectToAction; according to the docs:
Returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action.
When you pass that model when redirecting to an action, that data will be lost through the redirection action. What you probably want to do instead is make sure you pass the image URLs in the redirect URL or not use a redirect at all (just render a view)
There's really not enough to work with here to effectively answer your question, but there's nothing obviously wrong with the code you've provided. However, in order for any Image instances to be added to the list they must have been posted in the first place (i.e., if model.Files itself is empty, then so will be your IEnumerable<Image>.
To that end, there's two things you should check:
Ensure that your form has accept="multipart/form-data" on it. Otherwise, uploads will not be posted.
Ensure that your file upload field names are such that they'll bind to the model property on post. In your scenario here, they would need names like Files[N], where N is the index.
I am unable to use the drag-and-drop functionality within DotNetNuke version 7.1.
The drag-and-drop functionality of the Telerik RadEditor takes the browser's Base64 input and encases it in an img tag where the source is the data. E.g., src="data:image/jpeg;base64,[base64data]".
When using drag/drop to a RadEditor within the HTML Module and then saving the HTML content, that src definition is changed to a URI request by prepending the relative path for the DNN portal. E.g., src="/mysite/portals/0/data:image/jpeg;base64,[base64data]".
This converts what started out as a perfectly valid embedded image tag into a request and thereby causes the browser to request this "image" from the server. The server then returns a 414 error (URI too long).
Example without prepended relative path: http://jsfiddle.net/GGGH/27Tbb/2/
<img src="data:image/jpeg;base64,[stuff]>
Example with prepended relative path (won't display): http://jsfiddle.net/GGGH/NL85G/2/
<img src="mysite/portals/0/data:image/jpeg;base64,[stuff]>
Is there some configuration that I've missed? Prepending relative paths is OK for src="/somephysicalpath" but not for src="data:image...".
I ended up solving the problem prior to posting the question but wanted to add this knowledge to SO in case someone else encountered the same problem (has no one noticed this yet?). Also, perhaps, DNN or the community can improve upon my solution and that fix can make it into a new DNN build.
I've looked at the source code for RadEditor, RadEditorProvider and then finally the Html module itself. It seems the problem is in the EditHtml.ascx.cs, FormatContent() method which calls the HtmlTextController's ManageRelativePaths() method. It's that method that runs for all "src" tags (and "background") in the Html content string. It post-processes the Html string that comes out of the RadEditor to add in that relative path. This is not appropriate when editing an embedded Base64 image that was dragged to the editor.
In order to fix this, and still allow for the standard functionality originally intended by the manufacturer, the DotNetNuke.Modules.Html.EditHtm.ascx.cs, ManageRelativePaths needs to be modified to allow for an exception if the URI includes a "data:image" string at its beginning. Line 488 (as of version 7.1.0) is potentially appropriate. I added the following code (incrementing P as appropriate and positioned after the URI length was determined -- I'm sure there's a better way but this works fine):
// line 483, HtmlTextController.cs, DNN code included for positioning
while (P != -1)
{
sbBuff.Append(strHTML.Substring(S, P - S + tLen));
// added code
bool skipThisToken = false;
if (strHTML.Substring(P + tLen, 10) == "data:image") // check for base64 image
skipThisToken = true;
// end added code - back to standard DNN
//keep characters left of URL
S = P + tLen;
//save startpos of URL
R = strHTML.IndexOf("\"", S);
//end of URL
if (R >= 0)
{
strURL = strHTML.Substring(S, R - S).ToLower();
}
else
{
strURL = strHTML.Substring(S).ToLower();
}
// added code to continue while loop after the integers were updated
if (skipThisToken)
{
P = strHTML.IndexOf(strToken + "=\"", S + strURL.Length + 2, StringComparison.InvariantCultureIgnoreCase);
continue;
}
// end added code -- the method continues from here (not reproduced)
This is probably not the best solution as its searching for a hard coded value. Better would be functionality that allows the developers to add tags later. (But, then again, EditHtml.ascx.cs and HtmlTextController both hard code the two tags that they intend to post-process.)
So, after making this small change, recompiling the DotNetNuke.Modules.Html.dll and deploying, drag-and-drop should be functional. Obviously this increases the complexity of an upgrade -- it would be better if this were fixed by DNN themselves. I verified that as of v7.2.2 this issue still exists.
UPDATE: Fixed in DNN Community Version 7.4.0
Using Sitecore 6.5, when images are rendered on a web page, a URL such as the one below is used
~/media/OSS/Images/MyImage
But if you add an image from the library in a content editor a path such as below is used
~/media/1CFDDC34C94E460FAA2B1518DCA22360.PNG
This makes sense as it's trying to use a meaningful path when rendered for the web.
We would like to use the first media image path to add images in the content editor in HTML view rather than the default second method. This is because we are actually taking some html files and automatically adding them in to Sitecore via a script and we can change the image paths to a location in the media library if the first image format is used by using a convention so the images should appear in the newly created items. We have now idea about a media library image ID.
The first format does appear to work as images are rendered in the content editor design editor and when the page is rendered but Sitecore marks these as broken links in the Content Editor. Are any ideas on whether we are safe to use this format?
You may want to avoid hard coding paths to media in the rich text field. The second "dynamic link" is an important feature of Sitecore in that it keeps a connection between the media and item in the Links database. This safeguards you if you ever delete or move the media.
Since it sounds like you are importing content from an external source and you already have a means of detecting the image paths, I would recommend (if possible) that you upload the images programmatically and insert the dynamic links.
Below is a function that you can call for uploading to the Media Library and getting back the media item:
Example usage:
var file = AddFile("/assets/images/my-image.jpg", "/sitecore/media library/images/example", "my-image");
The code:
private MediaItem AddFile(string relativeUrl, string sitecorePath, string mediaItemName)
{
var extension = Path.GetExtension(relativeUrl);
var localFilename = #"c:\temp\" + mediaItemName + extension;
using (var client = new WebClient())
{
client.DownloadFile("http://yourdomain.com" + relativeUrl, localFilename);
}
// Create the options
var options = new MediaCreatorOptions
{
FileBased = false,
IncludeExtensionInItemName = false,
KeepExisting = false,
Versioned = false,
Destination = sitecorePath + "/" + mediaItemName,
Database = Factory.GetDatabase("master")
};
// Now create the file
var creator = new MediaCreator();
var mediaItem = creator.CreateFromFile(localFilename, options);
return mediaItem;
}
As for generating the dynamic link to the media, I actually haven't found a Sitecore method to do this, so I resorted to the following code:
var extension = !String.IsNullOrEmpty(Settings.Media.RequestExtension)
? Settings.Media.RequestExtension
: ((MediaItem)item).Extension;
var dynamicMediaUrl = String.Format(
"{0}{1}.{2}",
MediaManager.MediaLinkPrefix,
item.ID.ToShortID(),
extension);
No it will not cause any rendering issue apart from the broken links notification as you noted. Also when you select an image in the editor and select to edit the media folder will be at the root rather than at the image itself. But as Derek has noted, the use of dynamic links is an important feature to make sure your links do not break if something is moved or deleted.
I would add to his answer that since you are adding the text via a script you can detect images in the text using HtmlAgilityPack (already used in Sitecore) or FizzlerEx (more similar to jQuery syntax), use the code he provided to upload the images to the media library, grab the GUID and replace the src. Something along the lines of:
string content = "<whatever your html to go in the rich text field>";
HtmlDocument doc = new HtmlDocument();
doc.Load(content);
foreach(HtmlNode img in doc.DocumentElement.SelectNodes("//img[starts-with(#src, '/media/')]")
{
HtmlAttribute attr = img["src"];
Item scMediaItem = UploadLocalMedia(attr.Value);
attr.Value = GetDynamicMediaUrl(scMediaItem);
}
Is it possible to change the orientation on the printer when using the webbrowser control? I need to change it to landscape. If I need to change the printer defaults for the printer itself that would be ok to as I would just set them back after I was done. (That's what I currently have to do with printing to a non-default printer).
I currently use this to temporarealy set the default printer, then set it back when I'm done with my print job...
private string SetDefaultPrinter(string newDefaultPrinter)
{
//Get the list of configured printers:
string strQuery = "SELECT * FROM Win32_Printer";
string currentDefault = string.Empty;
System.Management.ObjectQuery oq = new System.Management.ObjectQuery(strQuery);
System.Management.ManagementObjectSearcher query1 = new System.Management.ManagementObjectSearcher(oq);
System.Management.ManagementObjectCollection queryCollection1 = query1.Get();
System.Management.ManagementObject newDefault = null;
foreach (System.Management.ManagementObject mo in queryCollection1)
{
System.Management.PropertyDataCollection pdc = mo.Properties;
if ((bool)mo["Default"])
{
currentDefault = mo["Name"].ToString().Trim();
if (newDefaultPrinter == null || newDefaultPrinter == string.Empty)
{
//Just need to know the default name
break;
}
}
else if (mo["Name"].ToString().Trim() == newDefaultPrinter)
{
//Save this for later
newDefault = mo;
}
}
//Reset the default printer
if (newDefault != null)
{
//Execute the method
System.Management.ManagementBaseObject outParams = newDefault.InvokeMethod("SetDefaultPrinter", null, null);
}
return currentDefault;
}
Anyone know how to change the orientation?
You can do this by using IE print templates. There are not too much documentation on this, but here below is another stack-overflow post that suggests some useful links on this regards, and it actually helped me a lot:
WebBrowser print settings
The most useful part was suggesting to view the standard IE print template by navigating to the below URL inside IE:
res://ieframe.dll/preview.dlg
And also you can view a related JavaScript file by navigating to the below URL inside IE:
res://ieframe.dll/preview.js
Those two files helped me a lot to understand what is going on in the background, and by changing the "Printer.orientation" value inside the "preview.js" file, I could successfully change the orientation of the printed HTML document.
//EDIT:
I was testing it wrong. The documentation referring to this registry key is for windows CE... So the correct answer is that it is not possible, as "explained" in the documentation: http://support.microsoft.com/kb/236777
A possible workaround is to rotate the whole page through css (transform:rotate(90deg)), but the relative position keeps being the old one, so for multiple pages is just a mess.
It is incredible that something so basic can't be done...
//OLD ANSWER:
I was looking for the same and finally found that you really can't change the printer settings (page orientation, header, footer, margins...) directly with the webbrowser component, the only way to do it is changing the registry key to set the default behaviour of internet explorer.
For the page orientation it would be:
Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true).SetValue("PageOrientation", 2);
You should keep the old value and restore it after printing.