How to customize WebViewPage executation output? - c#

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 :=)

Related

How to implement asp-append-version="true" to background-image property?

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).

MVC How to bundle html templates of type "text/html"?

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

How do I determine which View is used when calling View(Object model)

First, some context:
Language - C#
Platform - .Net Framework 4.5
Project type - ASP.Net MVC 4
I am trying to determine which View in an MVC project is handling an explicit call to the following method. The MSDN docs for the method are here: http://msdn.microsoft.com/EN-US/library/dd492930.aspx
protected internal ViewResult View(
Object model
)
The original Author is using a View to generate a PDF file with a third-party library. I need to modify the view to include additional information.
The problem: I'm having trouble finding which View to modify. There are hundreds of them, and (IMHO) they are poorly named and organized. The basic process for generating a PDF looks like this. I'm getting confused in between steps 3 and 4.
An Entity's ID is passed to an ActionResult
The Entity is retrieved from the backing store
The model is passed to the Controller.View method mentioned above:
var viewModel = View(model);
var xmlText = RenderActionResultToString(viewModel);
The resulting ViewResult is used with an instance of ControllerContext to generate HTML as if being requested by a browser.
The resulting HTML is passed to the third-party tool and converted to a PDF.
I understand everything else very clearly. What I don't understand is how the call to View(model) determines which View file to use when returning the ViewResult. Any help greatly appreciated!
I'm including the code below, in case it helps anybody determine the answer.
The ActionResult:
public ActionResult ProposalPDF(String id, String location, bool hidePrices = false)
{
var proposal = _adc.Proposal.GetByKey(int.Parse(id));
var opportunity = _adc.Opportunity.GetByKey(proposal.FkOpportunityId.Value);
ViewData["AccountId"] = opportunity.FkAccountId;
ViewData["AccountType"] = opportunity.FkAccount.FkAccountTypeId;
ViewData["Location"] = location;
ViewData["HidePrices"] = hidePrices;
return ViewPdf(proposal);
}
The ViewPDF method:
protected ActionResult ViewPdf(object model)
{
// Create the iTextSharp document.
var document = new Document(PageSize.LETTER);
// Set the document to write to memory.
var memoryStream = new MemoryStream();
var pdfWriter = PdfWriter.GetInstance(document, memoryStream);
pdfWriter.CloseStream = false;
document.Open();
// Render the view xml to a string, then parse that string into an XML dom.
var viewModel = View(model);
var xmlText = RenderActionResultToString(viewModel);
var htmlPipelineContext = new HtmlPipelineContext();
htmlPipelineContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
//CSS stuff
var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
var cssResolverPipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlPipelineContext, new PdfWriterPipeline(document, pdfWriter)));
var xmlWorker = new XMLWorker(cssResolverPipeline, true);
var xmlParser = new XMLParser(xmlWorker);
xmlParser.Parse(new StringReader(xmlText));
// Close and get the resulted binary data.
document.Close();
var buffer = new byte[memoryStream.Position];
memoryStream.Position = 0;
memoryStream.Read(buffer, 0, buffer.Length);
// Send the binary data to the browser.
return new BinaryContentResult(buffer, "application/pdf");
}
The RenderActionResultToString helper method:
protected string RenderActionResultToString(ActionResult result)
{
// Create memory writer.
var sb = new StringBuilder();
var memWriter = new StringWriter(sb);
// Create fake http context to render the view.
var fakeResponse = new HttpResponse(memWriter);
var fakeContext = new HttpContext(System.Web.HttpContext.Current.Request, fakeResponse);
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeContext), this.ControllerContext.RouteData, this.ControllerContext.Controller);
var oldContext = System.Web.HttpContext.Current;
System.Web.HttpContext.Current = fakeContext;
// Render the view.
result.ExecuteResult(fakeControllerContext);
// Restore data.
System.Web.HttpContext.Current = oldContext;
// Flush memory and return output.
memWriter.Flush();
return sb.ToString();
}
I'm not exactly sure what you're asking, but, when you call View(model) the view that is chosen is based upon conventions.
Here is an example:
public class HerbController : Controller {
public ActionResult Cilantro(SomeType model) {
return View(model)
}
}
That will look for a view file called Cilantro.cshtml in a folder called Herb (Views/Herb/Cilantro.cshtml). The framework will also look in the Shared directory as well in case it is a view that is meant to be shared across multiple results.
However, you may also want to look at the Global.asax file to see if there are any custom view paths being setup for the view engine. The example I gave above is based upon the default conventions of ASP.NET MVC. You can override them to meet your needs better if needed.
The convention for views is that they are in a folder named after the controller (without "Controller") and the .cshtml file inside that folder is named after the calling action. In your case, that should be:
~/Views/[Controller]/ProposalPdf.cshtml
The logic to determine which view template will be used is in the ViewResult that is returned from the call
var viewModel = View(model);
And how the view is selected is determined by the configured ViewEngine(s), but it will use the current Area, Controller and Action route values to determine what view should be served.
What the route values are for the ProposalPDF action will depend on how your routing is configured, but assuming the defaults, the action route value will be ProposalPDF, the controller route value will be the name of the controller class in which this action resides (minus the Controller suffix) and the area will be the area folder in which the controller lives, with a value of empty string if in the default controller folder. Then using these route values, a view will be looked up in the Views folder using the following convention
~/Views/{Area}/{Controller}/{View}.cshtml
There is always Glimpse that can help with providing runtime Diagnostics too, such as which View file was used to serve up the returned page, although I'm not sure how this would look when a ViewResult is executed internally to provide the contents of a file.

Programmatically rendering a web UserControl

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

Good ASP.NET method for checking, reading and returning file contents

I found a good way to check if a file exists and read the contents if it does, but for some reason I can't create a method out of it.
Here's what I have so far:
<script runat="server">
void Page_Load(Object s, EventArgs e) {
lblFunction.Text = mwbInclude("test.txt");
}
string mwbInclude(string fileName) {
string inc = Server.MapPath("/extra/include/" + Request["game"] + "/" + fileName);
string valinc;
if(System.IO.File.Exists(inc))
{
valinc = System.IO.File.ReadAllText(inc);
}
return valinc;
}
</script>
I wish I could provide more info, but the server this is on doesn't show any feedback on errors, just a 404 page.
I think
valinc = Response.Write(System.IO.File.ReadAllText(inc));
should be
valinc = System.IO.File.ReadAllText(inc);
Why are you setting the Text property and calling Response.Write? Do you want to render the text as a label, or as the whole response?
If you're getting a 404, it's because your page isn't being found, not because there's a problem with the script itself. Have you tried ripping out all of the code and just sticking in some HTML tags as a sanity check?

Categories