I am trying to implement the HTMLTagHelper asp-append-version="true" to my images.
The problem is as regards the DOM distribution, I am not assigning the attribute to an <img> tag but to to a <div> containing the image with the background-url property.
Moreover, the div is generated before all the DOM is loaded and I don't know if there would be a different approach of doing it.
One is obvious, change the div to an img tag, but I don't want it as my design has to remain the same.
My javascript has hitherto been like this:
cardHTML += '<div asp-append-version="true" class="card littleCard" style="background-image: url(/Content/Img/Especialistas/LittleCard/' + especialista.idEspecialista + '.jpg' + ')' + '" >';
cardHTML += '</div>';
The asp-append-version="true" won't work on the div tag.
Any ideas on how to find an approach of dealing with this ?
Thanks
You can create a custom TagHelper to target all elements having an inline style attribute. The following example I've tried looks working fine but if you want something more standard (similar to ImageTagHelper, ...), you can try looking into the base class UrlResolutionTagHelper. I'm not so sure why it need to be more complicated in there in which basically you need to resolve the URL before actually processing it more. I've tried with a simple IFileVersionProvider and it works for relative paths as well (of course the resolved path should be at the current server's web root).
The following simple example works fine for attribute values of HtmlString (which is almost the usual case, some custom rendering may inject IHtmlContent that is not of HtmlString, for such complicated cases, you can refer to the source code for UrlResolutionTagHelper, even copying almost the exact relevant code there is fine):
//target only elements having an inline style attribute
[HtmlTargetElement(Attributes = "style")]
public class InlineStyleBackgroundElementTagHelper : TagHelper
{
readonly IFileVersionProvider _fileVersionProvider;
const string BACKGROUND_URL_PATTERN = "(background(?:-image)?\\s*:[^;]*url)(\\([^)]+\\))";
public InlineStyleBackgroundElementTagHelper(IFileVersionProvider fileVersionProvider)
{
_fileVersionProvider = fileVersionProvider;
}
//bind the asp-append-version property
[HtmlAttributeName("asp-append-version")]
public bool AppendsVersion { get; set; }
//inject ViewContext from the current request
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (AppendsVersion)
{
if (output.Attributes.TryGetAttribute("style", out var styleAttr))
{
//the value here should be an HtmlString, so this basically
//gets the raw plain string of the style attribute's value
var inlineStyle = styleAttr.Value.ToString();
var basePath = ViewContext.HttpContext.Request.PathBase;
inlineStyle = Regex.Replace(inlineStyle, BACKGROUND_URL_PATTERN, m =>
{
//extract the background url contained in the inline style
var backgroundUrl = m.Groups[2].Value.Trim('(', ')', ' ');
//append the version
var versionedUrl = _fileVersionProvider.AddFileVersionToPath(basePath, backgroundUrl);
//format back the inline style with the versioned url
return $"{m.Groups[1]}({versionedUrl})";
}, RegexOptions.Compiled | RegexOptions.IgnoreCase);
output.Attributes.SetAttribute("style", inlineStyle);
}
}
}
}
Usage: just like how you use the asp-append-version on other built-in tag helps. (like in your example).
Related
Currently, I am working on a project to generate a Table of Content for pdf using itext What I have is a list of elements (itext.layout.Element objects).
I have created a dictionary< string, int> where we store (chapter title and start page number). I want to consider
<p class="Heading2ANOC" > paragraphs whose Class is Heading2ANOC are chapters title
Mycode :
var toc = new Dictionary<string,int>();
foreach (IElement element in elements)
{
Console.WriteLine(element.GetType().Name);
if (element.GetType().Name == "HtmlPageBreak")
{
continue;
}
else if (element.GetType().Name == "Paragraph")//need a method to check wheather the class is "Heading2ANOC" {
int count=pdf.GetNumberOfPages();
toc.Add("section" + i, count);//
i++;
}
document.Add((IBlockElement)element);
}
I am getting Elements by using follwing code :
string path = "path for the Html";
string html = File.ReadAllText(path);
IList<IElement> elements = HtmlConverter.ConvertToElements(html);
example Html element:
<div style="mso-element: para-border-div; border: solid #A6A6A6 2.25pt; padding: 3.0pt 4.0pt 3.0pt 4.0pt; background: #D9D9D9;">
<p class="Heading2ANOC"><span style="mso-bookmark: _Toc190800487;"><span style="mso-bookmark: _Toc377720650;"><span style="mso-bookmark: _Toc396995390;"><span style="font-size: 11.0pt; font-family: 'Open Sans',sans-serif; color: black; mso-color-alt: windowtext;">SECTION 1 <span style="mso-tab-count: 1;"> </span>Name of the section</span></span></span></span></p>
</div>
There is a cleaner (and more flexible) way to approach the task compared to the approach you are taking now, but it requires writing more code. Fortunately, the code is pretty basic.
To understand what needs to be customized, you need to understand how pdfHTML works a bit. Roughly speaking, it traverses the DOM tree in DFS order and converts the DOM tree into the element tree. Each tag is traversed by a tag worker and that tag worker produces an element as a result. The elements are flexible enough to contain any custom properties (as long as you use a unique property ID not used by iText), so you can set those properties in tag workers and use them later on. In this case you want to pass along class property/attribute.
First off, let's create a custom tag worker deriving from PTagWorker that will process all the paragraphs in the HTML and set a custom property:
public static readonly int CUSTOM_PROPERTY_ID = -10;
private class CustomPTagWorker : PTagWorker {
public CustomPTagWorker(IElementNode element, ProcessorContext context) : base(element, context) {
}
public override void ProcessEnd(IElementNode element, ProcessorContext context) {
base.ProcessEnd(element, context);
IPropertyContainer elementResult = GetElementResult();
if (elementResult != null && !String.IsNullOrEmpty(element.GetAttribute(AttributeConstants.CLASS))) {
elementResult.SetProperty(CUSTOM_PROPERTY_ID, element.GetAttribute(AttributeConstants.CLASS));
}
}
}
Then we need to use that tag worker somehow - for that we create a custom tag worker factory:
private class CustomTagWorkerFactory : DefaultTagWorkerFactory {
public override ITagWorker GetCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (TagConstants.P.Equals(tag.Name().ToLower())) {
return new CustomPTagWorker(tag, context);
}
return base.GetCustomTagWorker(tag, context);
}
}
All we need to do now is make pdfHTML aware of those customizations by passing the custom tag worker in the converter properties:
ConverterProperties properties = new ConverterProperties().SetTagWorkerFactory(new CustomTagWorkerFactory());
To test it out, we can iterate over the elements and check for the presence of our custom property (instead of checking for names of the classes):
String html = "<p class=\"Heading2ANOC\">hello</p><p>world</p>";
ConverterProperties properties = new ConverterProperties().SetTagWorkerFactory(new CustomTagWorkerFactory());
IList<IElement> elements = HtmlConverter.ConvertToElements(html, properties);
foreach (IElement element in elements)
{
if (element.HasProperty(CUSTOM_PROPERTY_ID)) {
String propertyValue = element.GetProperty<String>(CUSTOM_PROPERTY_ID);
Console.WriteLine(propertyValue);
}
}
Please bear in mind that for more complicated HTMLs where elements nest into each other you might want to perform the final analysis in a different way, e.g.
foreach (IElement element in elements)
{
if (element is AbstractElement<Div>) {
var children = (element as AbstractElement<Div>).GetChildren();
// analyze children
}
}
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 trying to generate a HTML document with RazorEngine (http://razorengine.codeplex.com/). Everything is mostly working, but the problem I have now is some of the HTML is being rendered correctly, and HTML that I have nested in that is being rendered as literal HTML, so rather than the browser displaying the tables & divs as expected, it's displaying e.g.
"<table></table><div></div>"
I kick off this process by calling the following:
string completeHTML = RazorEngine.Razor.Parse("InstallationTemplate.cshtml", new { Data = viewModels });
The completeHTML is then written to a file.
"InstallationTemplate.cshtml" is defined as:
#{
var installationReport = new InstallationReport(Model.Data);
}
<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
<!-- I would expect this to write the rendered HTML
in place of "#installationReport.DigiChannels()" -->
#installationReport.DigiChannels()
</div>
</body>
</html>
Where InstallationReport and DigiChannels are defined as follows:
public static class InstallationReportExtensions
{
public static string DigiChannels(this InstallationReport installationReport)
{
return installationReport.GetDigiChannelsHtml();
}
}
public class InstallationReport
{
public string GetDigiChannelsHtml()
{
// the following renders the template correctly
string renderedHtml = RazorReport.GetHtml("DigiChannels.cshtml", GetDigiChannelData());
return renderedHtml;
}
}
public static string GetHtml(string templateName, object data)
{
var templateString = GetTemplateString(templateName);
return RazorEngine.Razor.Parse(templateString, data);
}
After GetDigiChannelsHtml() runs and returns renderedHtml, the line of execution returns to TemplateBase.cs into the method ITemplate.Run(ExecuteContext context), which is defined as:
string ITemplate.Run(ExecuteContext context)
{
_context = context;
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
_context.CurrentWriter = writer;
Execute(); // this is where my stuff gets called
_context.CurrentWriter = null;
}
if (Layout != null)
{
// Get the layout template.
var layout = ResolveLayout(Layout);
// Push the current body instance onto the stack for later execution.
var body = new TemplateWriter(tw => tw.Write(builder.ToString()));
context.PushBody(body);
return layout.Run(context);
}
return builder.ToString();
}
When I inspect builder.ToString(), I can see it contains proper HTML for the InstallationTemplate.cshtml stuff, and escaped HTML for the DigiChannels.cshtml stuff. For example:
How can I get #installationReport.DigiChannels() to include the correct HTML instead of the escaped HTML that it's currently doing?
Have you tried:
#Raw(installationReport.DigiChannels())
Edit : I could use it in following way (MVC3)
#Html.Raw(installationReport.DigiChannels())
The alternative to #Raw is to change your API to return HtmlStrings in appropriate places
Represents an HTML-encoded string that should not be encoded again.
The default razor behaviour is to encode strings.
This is long-winded but should be easy for one of you knowledgable chaps to workout.
I have a DotNetNuke webpage with a dynamic login link. If you are not logged in the link will be 'login' and have the appropriate URL to a login popup. If you are logged in the link will be 'logout' and likewise have an the appropriate URL to the webpage that handles logout.
When the page determines if you are logged in or not the HTML link gets built with an attribute of : onclick="return dnnModal.show('http://blahblah.com....').
The code that does this:
loginLink.Attributes.Add(" onclick", "return " + UrlUtils.PopUpUrl(loginLink.NavigateUrl, this, PortalSettings, true, false, 200, 550));
Regardless of what the link is, the ID and Class always remain the same. My problem is that I would like to replace the login text with an image, infact a different image for login and logout. The issue here is that because the ID and Class stay the same I can't just do it via CSS as I normally would, but I have been able to style classes based on their attributes. I have tested this by finding out the output of the creation of the HTML link and styling the class based on the 'href' attribute for example:
a #dnn_dnnLogin_loginLink .LoginLink [href="http://some very very long dynamically created URL.aspx"]{ styles here }
The problem with this is the login/logout links change based on what page you are currently on.
I do know that each of the two rendered options has a uniqe attribue that I could style and that's their "Text" attribute. So quite simply how do I add this attribute to be rendered in HTML so that I can style it with CSS?
I have tried several variations such as:
loginLink.Attributes.Add(" onclick", "return " + UrlUtils.PopUpUrl(loginLink.NavigateUrl, this, PortalSettings, true, false, 200, 550) " Text", + loginLink.Text);
In the hope that what would be rendered would be something like:
onclick="return dnnModal.show('http://localhost/CPD/tabid/87/ctl/Login/Default.aspx?returnurl=%2fCPD.aspx&popUp=true',/*showReturn*/true,200,550,true,'')" Text="Login"
So I could style:
a #dnn_dnnLogin_loginLink .LoginLink [Text="Login"]{styles here}
a #dnn_dnnLogin_loginLink .LoginLink [Text="Logout"]{styles here}
But instead I get a generic error. I have tried various ways of writing the line without success, I just don't know the syntax.
Could someone point me in the right direction? I so hope I'm not barking up the wrong tree as this would be a really simple solution to my initial problem.
Thanks,
Edit - Code for the whole page if that helps?
using System;
using System.Web;
using System.Web.UI;
using DotNetNuke.Common;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Services.Exceptions;
using DotNetNuke.Services.Localization;
using DotNetNuke.UI.Modules;
namespace DotNetNuke.UI.Skins.Controls
{
public partial class Login : SkinObjectBase
{
private const string MyFileName = "Login.ascx";
public string Text { get; set; }
public string CssClass { get; set; }
public string LogoffText { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
try
{
if (!String.IsNullOrEmpty(CssClass))
{
loginLink.CssClass = CssClass;
}
if (Request.IsAuthenticated)
{
if (!String.IsNullOrEmpty(LogoffText))
{
if (LogoffText.IndexOf("src=") != -1)
{
LogoffText = LogoffText.Replace("src=\"", "src=\"" + PortalSettings.ActiveTab.SkinPath);
}
loginLink.Text = LogoffText;
}
else
{
loginLink.Text = Localization.GetString("Logout", Localization.GetResourceFile(this, MyFileName));
}
loginLink.NavigateUrl = Globals.NavigateURL(PortalSettings.ActiveTab.TabID, "Logoff");
}
else
{
if (!String.IsNullOrEmpty(Text))
{
if (Text.IndexOf("src=") != -1)
{
Text = Text.Replace("src=\"", "src=\"" + PortalSettings.ActiveTab.SkinPath);
}
loginLink.Text = Text;
}
else
{
loginLink.Text = Localization.GetString("Login", Localization.GetResourceFile(this, MyFileName));
}
string returnUrl = HttpContext.Current.Request.RawUrl;
if (returnUrl.IndexOf("?returnurl=") != -1)
{
returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?returnurl="));
}
returnUrl = HttpUtility.UrlEncode(returnUrl);
loginLink.NavigateUrl = Globals.LoginURL(returnUrl, (Request.QueryString["override"] != null));
if (PortalSettings.EnablePopUps && PortalSettings.LoginTabId == Null.NullInteger)
{
loginLink.Attributes.Add(" onclick", "return " + UrlUtils.PopUpUrl(loginLink.NavigateUrl, this, PortalSettings, true, false, 200, 550));
}
}
}
catch (Exception exc)
{
Exceptions.ProcessModuleLoadException(this, exc);
}
}
}
}
CSS classes are just designed for this purpose, and they are supported by all browsers that use CSS styling (even very old ones). You don't have to fight around with obscure selectors that are referencing some link that could change and break your styling again.
Since you said you already have a class assigned to these tags, you just want to specify an additional one. You can have more than one class assinged to a tag. See the W3C css class page for more info, in section 'Attribute Values':
Specifies one or more class names for an element. To specify multiple
classes, separate the class names with a space, e.g. . This allows you to combine several CSS classes for one
HTML element.
You can set the second class simply by appending it to the WebControl.CssClass string, separated by a space:
loginLink.CssClass = loginLink.CssClass + " login";
or
loginLink.CssClass = loginLink.CssClass + " logout";
this way you can access it via a single class selector or even the multiple class selector (only selects those tags that have both classes assigned) in your CSS style sheet:
.LoginLink.login { /* styles here */ }
.LoginLink.logout { /* styles here */ }
The text on the login/logout button is not stored in the Text="" attribute, but in the InnerHTML node. So your CSS selector would not apply. (I also think that the spacings in the selector are wrong, and that this solution would not support multilingual buttons, etc.)
Usually this type of styling would be implemented by in the Skin Editor (Admin/Skins/scroll down to section Skin Designer), where you select Skin or Container, File, Token=LOGIN, Setting=Text and LogoffText, and add a value src=path/to/a.gif. However, the skin designer seems to be broken in 6.1.x (bug report)
You might still try and have a look at the login.ascx and login.ascx.cs files in the admin\Skins directory of your DNN installation. Edit the code to assign loginLink.ImageUrl depending on Request.IsAuthenticated.
I would like to override Url.Content to append a query string parameter to the resulting string on Url.Content.
The reason being, I have a web application that I develop, and with each release, users must clear their cache to get the new css and js. A solution for this is to append a version number to the querystring to force loading of the new version.
A working solution is as follows:
#{ var version = "?v=" + ViewBag.VersionNumber; }
<head>
<link href="#Url.Content("~/ux/css/base.css")#version" rel="stylesheet" type="text/css" />
</head>
Version is set in a config file so with each release, the version is updated. I would like this to be more automatic though, as currently any time a new css reference is added, we must remember to add #version to the string. An extension method the returns the path with the version number already appended would be perfect.
Also, if anyone knows who I could make changing the version number automatic with TFS check-ins or compiles that would be really useful too.
You could do something like this:
public static string VersionedContent(this UrlHelper urlHelper, string contentPath)
{
string result = urlHelper.Content(contentPath);
var versionService = Engine.IocService.GetInstance<IVersionService>();
string tag = versionService.GetVersionTag();
if (result.Contains('?'))
{
result += "&v="+tag;
}
else
{
result += "?v="+tag;
}
return result;
}
Version Service could look something like this:
public class VersionService : IVersionService
{
string _versionTag;
public VersionService()
{
_versionTag = Assembly.GetExecutingAssembly().GetName().Version.ToString();
_versionTag = _versionTag.Replace('.', '-');
}
#region IVersionedContentService Members
public string GetVersionTag()
{
return _versionTag;
}
#endregion
}
You might want to take a look at cassette
* EDIT *
For autom. build numbers with TFS, check out:
automatic-assembly-file-version-numbering-in-tfs-2010