I have a load of UserControl objects (ascx files) in their own little project. I then reference this project in two projects: The REST API (which is a class library project) and the main website.
I'm sure this would be easy in the website, simply use Controls.Add in any Panel or ASP.NET control would work.
However, what about the API? Is there any way I can render the HTML of this control, simply by knowing the type of the control? The RenderControl method doesn't write any HTML to the writer as the control's life cycle hasn't even started.
Please bare in mind that I don't have the controls in the web project, so I don't have a virtual path to the ascx file. So the LoadControl method won't work here.
All the controls actually derive from the same base control. Is there anything I can do from within this base class that will allow me to load the control from a completely new instance?
This is what I have done recently, works well, but understand postbacks will not work if you use it inside your ASP.NET app.
[WebMethod]
public static string GetMyUserControlHtml()
{
return RenderUserControl("Com.YourNameSpace.UI", "YourControlName");
}
public static string RenderUserControl(string assembly,
string controlName)
{
FormlessPage pageHolder =
new FormlessPage() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath }; //allow for "~/" paths to resolve
dynamic control = null;
//assembly = "Com.YourNameSpace.UI"; //example
//controlName = "YourCustomControl"
string fullyQaulifiedAssemblyPath = string.Format("{0}.{1},{0}", assembly, controlName);
Type type = Type.GetType(fullyQaulifiedAssemblyPath);
if (type != null)
{
control = pageHolder.LoadControl(type, null);
control.Bla1 = "test"; //bypass compile time checks on property setters if needed
control.Blas2 = true;
}
pageHolder.Controls.Add(control);
StringWriter output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
return output.ToString();
}
public class FormlessPage : Page
{
public override void VerifyRenderingInServerForm(Control control)
{
}
}
Related
I have a certain folder with a couple of view classes (XAML files).
Right now i am instantiating these by code:
engineRoomView = new EngineRoomView()
{
DataContext = new ProcessViewModel()
};
and then further down:
item = new TabItem();
item.Contents = engineRoomView;
item.Name = "Engine Room";
views.Add(item);
What I want to achieve is some kind of dynamic code for creating one instance of each view in that particular folder without knowing about them during programming.
If a developer adds another xaml file to that folder. Then this gets created in run-time.
Something imaginary like:
Foreach(file in folder)
{
magicInstance = createInstanceFromFile(file);
MainViewModel.addView(magicInstance);
}
Is this possible?
If I understand you correctly, this could be archived with the build in Xaml Reader. The Xaml Reader can read a xaml file and will generate the objects based on the xaml.
Have a look here:
Loading XAML at runtime?
It sounds like you have a "Parent View" that you want to automatically attach a child view for each file in the same folder.
If the classes in each folder have a namespace consistent with the folder structure, this code should allow you to create a list of an instances of each class in the same folder as an example instance that inherit from a base class (could modify easily for interface also).
static class NamespaceHelper
{
public static List<Type> FindTypesInSameNamespaceAs(object instance)
{
string ns = instance.GetType().Namespace;
Type instanceType = instance.GetType();
List<Type> results = instance.GetType().Assembly.GetTypes().Where(tt => tt.Namespace == ns &&
tt != instanceType).ToList();
return results;
}
public static List<T> InstantiateTypesInSameNamespaceAs<T>(object instance)
{
List<T> instances = new List<T>();
foreach (Type t in FindTypesInSameNamespaceAs(instance))
{
if (t.IsSubclassOf(typeof(T)))
{
T i =(T) Activator.CreateInstance(t);
instances.Add(i);
}
}
return instances;
}
}
Just call NamespaceHelper.InstantiateTypesInSameNamespaceAs<YourBaseViewType>(instanceOfParentViewInSameFolder), loop through the results, and add them to your Parent.
Foreach(ViewBase v in NamespaceHelper.InstantiateTypesInSameNamespaceAs<ViewBase>(this))
{
MainViewModel.addView(v);
}
I'm using TuesPechkin (the C# wrapper of wkhtmltopdf) and have it generating PDF files from HTML.
However, I would like to set the --disable-smart-shrinking option, which is listed in the wkhtmltopdf documentation as a PageOption
How can I do that?
public sealed class PdfConverter
{
static readonly PdfConverter instance = new PdfConverter();
private IConverter converter;
static PdfConverter()
{
}
PdfConverter()
{
// Keep the converter somewhere static, or as a singleton instance! Do NOT run this code more than once in the application lifecycle!
this.converter = new ThreadSafeConverter( new RemotingToolset<PdfToolset>( new Win32EmbeddedDeployment( new TempFolderDeployment())));
}
public static PdfConverter Instance
{
get { return instance; }
}
public byte[] ConvertHtmlToPdf(string html)
{
var document = new HtmlToPdfDocument
{
Objects = { new ObjectSettings { HtmlText = html } }
// Where are PageOptions? Thats where --disable-smart-shrinking is
};
return converter.Convert(document);
}
}
The --disable-smart-shrinking option does not exist in the API -- well, it kind of does, but in the form of it's opposite sibling: --enable-smart-shrinking.
That property is available in the TuesPechkin API as WebSettings.EnableIntelligentShrinking as seen in the TuesPechkin source code. It was named that way in TuesPechkin because that is how it is named in wkhtmltopdf's API as seen in the wkhtmltopdf source code.
You can also see there that the default value is true (from wkhtmltopdf), so if you set WebSettings.EnableIntelligentShrinking to false you should get the result you're aiming for.
It seems this functionality hasn't been implemented in Tuespechkin. I can't find it here, where most of the page options are located.
I guess he forgot to implement the option, so probably best to request the feature here. Or you can also add the feature yourself. :)
I have a series of template files (*.html) that are used by some JS components. Instead of having those JS components write the templates to the DOM when loaded, I wanted to bundle them together like scripts and have them downloaded separately by the client. This way should be faster (clientside), allow for caching (less round trips), and be more readable (template doesn't have to be stored in a JS string which breaks highlighting/intellisense).
How can this be accomplished?
I.
Use BundleTransformer [http://bundletransformer.codeplex.com/] and Mustache templates [https://mustache.github.io/] or Handlebars [http://handlebarsjs.com/]
II.
[Angular example but you can inspire a lot]
I'm not saying this is the best approach for your case, but i cannot left it like a comment.
Here is an example where the OP stores his bundle in $templateCache. Angular has a templateCache object, which stores all the templates it has loaded so far. It also lets you pre-load templates into the template cache.
Create a BundleTransform class, as he did:
public class PartialsTransform : IBundleTransform
{
private readonly string _moduleName;
public PartialsTransform(string moduleName)
{
_moduleName = moduleName;
}
public void Process(BundleContext context, BundleResponse response)
{
var strBundleResponse = new StringBuilder();
// Javascript module for Angular that uses templateCache
strBundleResponse.AppendFormat(
#"angular.module('{0}').run(['$templateCache',function(t){{",
_moduleName);
foreach (var file in response.Files)
{
// Get the partial page, remove line feeds and escape quotes
var content = File.ReadAllText(file.FullName)
.Replace("\r\n", "").Replace("'", "\\'");
// Create insert statement with template
strBundleResponse.AppendFormat(
#"t.put('partials/{0}','{1}');", file.Name, content);
}
strBundleResponse.Append(#"}]);");
response.Files = new FileInfo[] {};
response.Content = strBundleResponse.ToString();
response.ContentType = "text/javascript";
}
}
But you can store the templates where you want [i don't know where you want to store them].
Then create a Bundle.
public class PartialsBundle : Bundle
{
public PartialsBundle(string moduleName, string virtualPath)
: base(virtualPath, new[] { new PartialsTransform(moduleName) })
{
}
}
And you can use it like a ScriptBundle or StyleBundle.
bundles.Add(new PartialsBundle("testSPA", "~/bundles/partials").Include(
"~/Partials/nav-bar.html",
"~/Partials/home-page.html",
"~/Partials/investment-filter.html",
"~/Partials/investments-component.html",
"~/Partials/sector-component.html",
"~/Partials/transactions-component.html"));
And render like this: #Scripts.Render("~/bundles/partials")
In production transforming in this:
<script src="/bundles/partials?v=dq0i_tF8ogDVZ0X69xyBCdV2O2Qr3nCu0iVsatAzhq41"></script>
This guy is using the $templateCache object forcing Angular not to dynamically download template when are needed.
Further reading here: http://blog.scottlogic.com/2014/08/18/asp-angular-optimisation.html
I'm developing a Windows WPF application that uses the default WebBrowser control (System.Windows.Controls.WebBrowser) to embed web pages. Using the COM object that underlies the WPF control, I am able to manipulate as needed every HTML document loaded inside the control. Just as an example, here is a snippet of the code I use to get a handle of the COM object:
public void HookInputElementForKeyboard()
{
HTMLDocument htmlDocument = (HTMLDocument)webBrowserControl.Document;
var inputElements = htmlDocument.getElementsByTagName("input");
HTMLDocumentEvents_Event documentEvents = (HTMLDocumentEvents_Event) htmlDocument;
documentEvents.onclick += documentEvents_onclick;
DeregisterAll();
foreach (var item in inputElements)
{
DispHTMLInputElement inputElement = item as DispHTMLInputElement;
if (inputElement.type == "text" || inputElement.type == "password" || inputElement.type == "search")
{
HTMLButtonElementEvents_Event htmlButtonEvent = inputElement as HTMLButtonElementEvents_Event;
this.hookedElements.Add(htmlButtonEvent);
htmlButtonEvent.onclick += InputOnClickHandler;
htmlButtonEvent.onblur += InputOnBlurHandler;
}
}
}
Where I use dependencies from Microsoft.mshtml.dll.
Here I attach handlers to the events of the DOM elements to be able to manage them in .NET code (onclick and onblur events).
With that object (HTMLDocument) I can overcome almost every limitation of the WPF control.
My problem arises when the WebBrowser control navigates to a PDF document (i.e. the response MIME type is application/pdf). In this case I have to assume that the default Adobe Reader plugin is used to show the PDF (this is how my target machine has to behave). I can still get a handle of the underlying AcroRead ActiveX that is used with the following:
private void HookHtmlDocumentOnClick()
{
var document = (IAcroAXDocShim)WebBrowserControl.Document;
// Just a test
var obj = document as AcroPDF;
obj.OnMessage += obj_OnMessage;
}
After I added the proper dependencies to the project (Interop.AcroPDFLib.dll)
But at this point I do not know if there is a way to register for the mouse events happening on the document. All I have to do is to handle the click event on the PDF document.
of course, using the following, does not work. The event does not bubble up to the .NET code level.
WebBrowserControl.MouseDown += WebBrowserControl_MouseDown;
Does Anybody know if there is a way to hook the IAcroAXDocShim in order to do handle mouse-click events?
Any possible alternative? Should I rather go on a complete different path?
Does using directly the AcroRead ActiveX give me some advantages over the current scenario?
I ended up following a different approach. I decided to switch to a different UserControl, instead of the WPF WebBrowser, whenever a PDF content is recognized.
I knew that some classes and API for PDF rendering are available when you develop a Windows Store App, so I applied a solution in order to use those classes in my WPF application.
First I edited the csproj file adding the following tag as a child of <PropertyGroup>:
<TargetPlatformVersion>8.1</TargetPlatformVersion>
then a section named "Windows 8.1" should appear in the Add Reference dialog window. I added the only assembly of this section to the references. Some more assemblies may be necessary in order to make the solution work:
System.IO
System.Runtime
System.Runtime.InteropServices
System.Runtime.InteropServices.WindowsRuntime
System.Runtime.WindowsRuntime
System.Threading
System.Threading.Tasks
Once I had those, I wrote a render method that goes through the pages of the PDF file and creates a png file for each page. Then I added the pages to a StackPanel. At this point, you can manage the Click (or MouseDown) event on the StackPanel as you normally would on any UIElement.
Here is a brief example of the rendering methods:
async private void RenderPages(String pdfUrl)
{
try
{
HttpClient client = new HttpClient();
var responseStream = await client.GetInputStreamAsync(new Uri(pdfUrl));
InMemoryRandomAccessStream inMemoryStream = new InMemoryRandomAccessStream();
using (responseStream)
{
await responseStream.AsStreamForRead().CopyToAsync(inMemoryStream.AsStreamForWrite());
}
var pdf = await PdfDocument.LoadFromStreamAsync(inMemoryStream);
if (pdf != null && pdf.PageCount > 0)
{
AddPages(pdf);
}
}
catch (Exception e)
{
throw new CustomException("An exception has been thrown while rendering PDF document pages", e);
}
}
async public void AddPages(PdfDocument pdfDocument)
{
PagesStackPanel.Children.Clear();
if (pdfDocument == null || pdfDocument.PageCount == 0)
{
throw new ArgumentException("Invalid PdfDocument object");
}
var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
StorageFolder rootFolder = await StorageFolder.GetFolderFromPathAsync(path);
var storageItemToDelete = await rootFolder.TryGetItemAsync(Constants.LOCAL_TEMP_PNG_FOLDER);
if (storageItemToDelete != null)
{
await storageItemToDelete.DeleteAsync();
}
StorageFolder tempFolder = await rootFolder.CreateFolderAsync(Constants.LOCAL_TEMP_PNG_FOLDER, CreationCollisionOption.OpenIfExists);
for (uint i = 0; i < pdfDocument.PageCount; i++)
{
logger.Info("Adding PDF page nr. " + i);
try
{
await AppendPage(pdfDocument.GetPage(i), tempFolder);
}
catch (Exception e)
{
logger.Error("Error while trying to append a page: ", e);
throw new CustomException("Error while trying to append a page: ", e);
}
}
}
I am going to inject some elements in every view of our Asp.Net MVC application. so to do that (I found this: Custom WebViewPage inject code when razor template is rendering answer) I have subclassed the WebViewPage and I have overridden the ExecutePageHierarchy like below:
public override void ExecutePageHierarchy()
{
base.ExecutePageHierarchy();
string output = Output.ToString();
if (MyHelper.InitializationRequired)
output = MyHelper.GetPageHeader() + output + MyHelper.GetPageFooter();
//--------------
Response.Clear();
Response.Write(output);
Response.End();
}
by this code we can wrap all of the output markup with some header and some footer elements such as scripts or additional tags which i want to do that.
BUT in this way we lost the Layout completely! because of clearing the response.
my main question is that, how to inject some HTML markups exactly before or exactly after WebViewPage's output by preserving the content of response which maybe there are some other views or the layout?
Finally i found a trick to do it by this way:
public override void ExecutePageHierarchy()
{
var tmp = OutputStack.Pop();
var myWriter = new StringWriter();
OutputStack.Push(myWriter);
base.ExecutePageHierarchy();
tmp.Write(
string.Format("<div> Header of [{0}]</div> {1} <div> Footer of [{0}]</div>",
VirtualPath,
myWriter.ToString()));
}
it works well generally and it wraps the output of view by header and footer. but in my scenario i should able to access to some flags which will assigned on executing view. so i must check them after view execution:
public override void ExecutePageHierarchy()
{
var tmp = OutputStack.Pop();
var myWriter = new StringWriter();
OutputStack.Push(myWriter);
base.ExecutePageHierarchy();
if (MyHelper.InitializationRequired)
tmp.Write(
string.Format("<div> Header of [{0}]</div> {1} <div> Footer of [{0}]</div>",
VirtualPath,
myWriter.ToString()));
else
tmp.Write(myWriter.ToString());
}
this approach works well for me. so i posted it, maybe help some one;)
You should use the layout system of MVC ... It's full featured to have a master layout schema :=)