Dynamically replacing content in views - asp.net-mvc

I have a situation where our core project requires heavy templating.
We have come up with a solution which should make the project very flexible, but I am unsure how to implement a solution.
In our views I want to be able to place custom tags like this:
<div class="some view">
{{SomeTag}}
</div>
Now these tags don't have anything to do with the model, what we want to do is to replace these tags at runtime on the server (not the browser!) with the contents of a file on the server (under some designated directory) called "SomeTag.html".
Is there some way to add a method or override a method in a Base Controller (which will inherit "Controller") which searches through the output of the view for any {{Tag}} and does a replace with it's corresponding Tag.html file?
For example perhaps this is appropriate?
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
}

You could use a modified form of
HtmlHelper.Partial(partialViewName)
You can write an extension method specific to your scenario
public static MvcHtmlString Template(this HtmlHelper htmlHelper, string templateKey)
{
string partialViewName = null;
// get partialViewName according to the template key
return htmlHelper.Partial(partialViewName);
}
You will use it like
#Html.Template("SomeTag")

Related

How do I create something similar to the HTMLHelper namespace for the business name?

I would like to create something that can be called into any view in a website like this
#MyBusinessName.TelephoneNumber
#MyBusinessName.Address
etc...
In the same way I would call a HtmlHelper
#Html.ActionLink()
How do I add a namespace like "MyBusinessName" for use in an MVC view?
Html in Razor pages is a property of the class that pages inherit from, so of course you can't implement what you want in the same way as the standard HTML helper works. But I can suggest a different solution.
You may define a static class in your project that will work as your own helper (define there static methods/properties you want to use in views). I'd suggest something like the following:
using System.Web.Mvc;
namespace YourProject.Infrastructure {
public static class YourBusinessHelper {
public static MvcHtmlString TextBox(string name) {
string html = string.Format("<input name=\"{0}\" />", name);
return new MvcHtmlString(html);
}
// ...
}
}
System.Web.Mvc.MvcHtmlString is a class representing HTML markup that an HTML helper method inserts into the view where it is used. You may create it using a constructor that receives a string parameter containing the needed HTML markup — like in my code.
Now you can use this class in your views. You just need to import the containing namespace with this Razor instruction:
#using YourProject.Infrastructure
I'd suggest to put it into the view start file (_ViewStart.cshtml) so that it applies to all your views.
The sample helper method above can be used simply:
#YourBusinessHelper.TextBox("TextBoxName")
UPDATE: you may also create an extension method for HtmlHelper:
using System.Web.Mvc;
namespace YourProject.Infrastructure {
public static class YourBusinessHelper {
public static string CompanyPhoneNumber(this HtmlHelper helper) {
return "+82649256720";
}
// ...
}
}
You may use string not MvcHtmlString in helper methods if they return plain text (no HTML markup).
Use it like a built-in helper method:
#:Call us: #Html.CompanyPhoneNumber()

In ASP.Net MVC, how can you manage dependencies between views and layout pages?

We're working with ASP.Net MVC and Google Publisher Tags (GPT).
GPT requires that you create javascript "slots" in the <head> and some html+script that goes into the <body>. The key dependency here is that in both places is an id that must match. So the head will contain some Javascript that includes something like:
<head>
<script>
...
DefineSlot('div-gpt-ad-123456789-0', 'foo', 'bar')
...
</script></head>
and
<body>
...
<div id='div-gpt-ad-123456789-0'>
<script>
...
Display('div-gpt-ad-123456789-0')
...
</script>
</div>
...
How can we manage the dependencies between these 2 pieces code? The critical piece is that the id of both parts must match.
We want to use MVC to create these pieces of code dynamically. So in any view, partial or layout, I will be able to add a helper call that might look like:
#Html.CreateAd(size, "foo", "bar")
#Html.CreateAd can be called anywhere in a view, partial view, layout, or nested layout.
How do you use ASP.Net MVC to program the code that goes into <head>?
Any suggestions are appreciated. I'm just looking for direction, not a full blown solution.
Many Thanks.
You have a few different ways to do this.
You can add the id's to the ViewData or a base viewmodel.
Then OnActionExecuting or OnActionExecuted in a base controller or via actionfilters, you can add your data to whichever place you prefer to. If you need examples for this, please leave a comment on this answer.
Then, your helpers (one for each section) you can read from one of the 2 sources you decided on. I have gone both routes. If all of your pages are going to have an ad, then I would lean towards the base ViewModel. If it's more of a rare occurrence ViewData would be more appropriate.
To access the viewdata within an htmlHelper Extension method:
public static class HtmlExtension
{
public static MvcHtmlString RenderAdHead(this HtmlHelper h)
{
h.ViewContext.ViewData.Model // a test and cast here
h.ViewContext.ViewData["AdIdentifier"] // test for null and cast here
string tags = String.Empty;
//build up string to resemble your script/html tags using either of the
//sources above, so long as either source is not empty.
return new HtmlMvcString(tags);
}
}
And some code for a filter:
public class AdvertisingFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
List<String> idList = null; // list of string might not be adequate. depends on your implementation
//read advertising ids from your datastore or wherever you have.
filterContext.Controller.ViewData["advertisingFilter"] = idList;
}
}
The basecontroller is pretty much the same, instead you have the controllercontext directly. You just have to be sure all your controllers inherit from them.

Enforce usage of custom HtmlHelper extensions over MVC's

Asp.net MVC provides lots of (and very useful) HtmlHelper extensions. But what if I was to provide a micro sub-framework with some extension methods that extend existing ones?
i.e. BeginForm may be rewritten to be more rich (always adding security stuff like anti forgery token and similar.
Question
In order to not rewrite all of the Asp.net MVC's HTML helper methods how can I enforce usage of mine? So that the using usual BeginForm would either throw an exception or not be accessible in the first place. The second choice is likely not possible without removing System.Web.Mvc.Html namespace from view's folder web.config file. This would mean that all of those helpers would need rewriting. And that's something I don't want to do.
The thing is that when this micro sub-framework is used it should prevent usage of standard helpers for security reasons. Period.
What other options are there for me?
Example
Suppose I would only write my own BeginForm that I would call BeginSecureForm so one would use it as:
#using Html.BeginSecureForm() {
...
#Html.EditorFor(m => m.Something)
...
}
As you can see I've used my custom helper and standard EditorFor helper as well. This means that System.Web.Mvc.Html is still included to use non-custom helpers like EditorFor.
Upper code works fine as long as you use my custom helper method... But what if some developer would forget to do so and use the normal one instead?
#using Html.BeginForm() {
...
#Html.EditorFor(m => m.Something)
...
}
Well in this case I would either like to:
Html.BeginForm not being accessible at all
Html.BeginForm throws an exception that the secure version should be used
anything else I don't know can be done to prevent usage of standard BeginForm
One possibility to achieve that is to write a custom WebViewPage and override the Html property with a custom one:
public abstract class MyWebViewPage<T> : WebViewPage<T>
{
public override void InitHelpers()
{
this.Ajax = new AjaxHelper<T>(ViewContext, this);
this.Html = new MyHtmlHelper<T>(ViewContext, this);
this.Url = new UrlHelper(ViewContext.RequestContext);
}
public new MyHtmlHelper<T> Html { get; set; }
}
and here's the custom MyHtmlHelper<T> class in which you will make obsolete the methods that you don't want to be used directly by the developers:
public class MyHtmlHelper<T>: HtmlHelper<T>
{
public MyHtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
: base(viewContext, viewDataContainer)
{
}
[Obsolete("Use SecureBeginForm instead", true)]
public MvcForm BeginForm()
{
throw new Exception("Use SecureBeginForm instead.");
}
}
Alright, now all that's left to do is to switch the base type for all Razor views in the application. This could be done inside ~/Views/web.config where you will replace:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
with:
<pages pageBaseType="MyAppName.Mvc.MyWebViewPage">
OK, now you could write your micro framework extension methods to the MyHtmlHelper class and thus providing your custom secure counterparts of the default methods:
public static class MyHtmlHelperExtensions
{
public static MvcForm SecureBeginForm<T>(this MyHtmlHelper<T> html)
{
var rawUrl = html.ViewContext.HttpContext.Request.Url.AbsoluteUri;
var builder = new UriBuilder(rawUrl);
builder.Scheme = Uri.UriSchemeHttps;
var form = new TagBuilder("form");
form.Attributes["action"] = builder.ToString();
form.Attributes["method"] = "post";
html.ViewContext.Writer.Write(form.ToString());
return new MvcForm(html.ViewContext);
}
}
And now inside any Razor view:
#using (Html.SecureBeginForm())
{
...
}
and when you attempt:
#using (Html.BeginForm())
{
...
}
you get a compile-time error (assuming you have enabled compilation of Razor views):
or a runtime exception if you haven't.
You are trying to achieve security in the application by forcing developers to avoid using the Html.BeginForm instead of using the secured one. Let say you somehow tricked the framework not to use Html.BeginForm so what? an young developer who is even not aware of Html.BeginForm can directly write the form HTML in the view and break the rule!
I think security has to be implemented in an application by not forcing someone to use the right tool instead of that it has to be done at the higher level of the application. In the form example itself if all the HTML forms posted to the server should have an anti forgery token then I would do the check at the higher level in the MVC pipeline. If some developer used the normal form still that module won't work and that will be taken care in the testing phase.

ASP.NET MVC - Converting a complex control to be MVC-friendly

I have an ASP.NET WebForms control (derived from Control, not WebControl, if it helps) that has a rather complicated Render() function. The control has no viewstate and only uses the Control approach so it can render output directly. I feel it's a fine candidate for working with the MVC approach.
I'd like to use it in an MVC application I'm using, however I don't know what's the best way to go about it.
At first I thought about converting it to a HTML Helper method, but my control renders a large amount of HTML so the Helper method (with it returning strings) isn't too attractive.
The alternative is a PartialView, but those are UserControl derivatives, which wouldn't be appropriate in this case.
I see other HTML Helper methods don't return HtmlString, but actually use HtmlHelper.ViewContext.Writer to write output directly, but according to this question ( HtmlHelper using ViewContext.Writer not rendering correctly ) he was getting strange results. I'd like to avoid that mishap.
EDIT:
I think I've solved it using the HtmlHelper.ViewContext.Writer approach, and I haven't experienced the same problem as the problem I quoted.
Here's the code I wrote:
public static class MiniViewHelper {
public static void RenderMiniView<TModel>(this HtmlHelper html, MiniView<TModel> view, TModel model) {
TextWriter wtr = html.ViewContext.Writer;
HtmlTextWriter hwtr = wtr as HtmlTextWriter;
if( hwtr == null ) hwtr = new HtmlTextWriter( wtr );
view.Render( hwtr );
}
}
public abstract class MiniView<TModel> {
public TModel Model { get; set; }
public abstract void Render(HtmlTextWriter wtr);
}
public class VeryComplicatedMiniView : MiniView<ComplicatedViewModel> {
public override void Render(HtmlTextWriter wtr) {
wtr.WriteLine("VeryComplicatedMiniView ");
}
}
Used like so from my pages:
<% Html.RenderMiniView( new VeryComplicatedMiniView () { Propery1 = foo }, Model.RelevantMiniViewModel ); %>
Any thoughts?
The two approaches you have outlined in your question are correct. You could either try to write a custom HTML helper which will spit the same HTML as the control or use a partial.
I see other HTML Helper methods don't return HtmlString, but actually
use HtmlHelper.ViewContext.Writer to write output directly
ViewContext.Writer should be fine. Returning an IHtmlString from the helper is also fine. Just make sure you are properly encoding it inside since IHtmlString will not be automatically HTML encoded in Razor and it supposes that the helper takes care of this. Using a TagBuilder to generate a DOM tree in a helper is a good approach.

ASP MVC Razor view extension methods, how to create 'global' view methods?

I am using Razor view with asp mvc preview 3
I am trying to create some methods which I would like available directly in the views. These are not really Html helper methods so I don't think extending HtmlHelper makes sense?
my goal, be able to call methods in the view i.e.
#HelloWorld(); vs #Html.HelloWorld()
I can get Html.HelloWorld to work by creating an extension method on HtmlHelper
public static class HtmlExtensions
{
public static string HelloWorld(this HtmlHelper helper)
{
return "Hello";
}
}
I would like to do the same thing but for the view; my problem - what type of object is the view?
Note: I was able to get this to work by defining the methods in the .cshtml page
#functions
{
public string HelloWorld()
{
return "Hello";
}
}
#HelloWorld() #* now this works *#
then I tried to put this code my _viewstart.cshtml file thinking it would be available in all views but it was not
if I knew which type the view was I think it could be easily extended, any help appreciated
As remarked by others, Razor Views all ultimately inherit from WebViewPage:
public abstract class WebViewPage<TModel> : WebViewPage
You can therefore simply write your extension methods for WebViewPage without creating a new base class or changing the config files, which has been suggested by other answers. For example:
public static class WebViewPageExtensions
{
public static string HollowWorld(this WebViewPage wvp)
{
return "Memento mori";
}
}
Add a using statement for that namespace to your View and then:
<p>#this.HollowWorld()</p>
it turns out, the asp runtime is going to define the Execute method at runtime, so the custom view base class must also be abstract
using System;
using System.Web.Mvc;
namespace MyMvcWebApp.Extensions
{
public abstract class ViewBase<TModel>
: System.Web.Mvc.WebViewPage<TModel> where TModel : class
{
// now this will be available in any view #HelloWorld()
public string HelloWorld()
{
return "Hello from the ViewBase class";
}
}
}
this should work with strongly typed views, it looks like with razor all views are strongly typed, when you do not define the type 'dynamic' is used and that is the strong type
also, as Clicktricity stated you then update the web.config (the one under the Views directory)
<pages pageBaseType="MyMvcWebApp.Extensions.ViewBase">
The default base class for Razor views is specified in the Web.config located in the views directory. Usually it is:
<pages pageBaseType="System.Web.Mvc.WebViewPage">
I've not tried it, but I would suggest inheriting from this base class and adding your own functionality, then adjust the web.config accordingly.
The best way to call a method with arguments using razor engine is to use helpers.
Example: let you have a helper #MakeNote(string content)
Then in cshtml page you just need to call #MakeNote("Hi") and you should be fine.
I was going crazy when I was having problem then google sent me to this page but it did not help. I was trying to load content in html <select> with L2E using razor.
The secret is to create a helper in app_code then use with cshtml.

Resources