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.
Related
In the new release of ASP CORE 2.0 last month, they introduced Razor Pages, this has me in a loop since the old controller & the model frpm MVC are missing from ASP CORE 2 Razor pages.
My understanding from this page is that we get default binding of the properties using a [BindProperty] attribute outside the Action/Method!!!!, this is because its moved to an MVVM framework as opposed to MVC framework.
Question: While trying to rewrite the traditional actions, since there are no controllers, how to move to the code to the new RazorPages MVVM framework, i.e. and where and how to bind the properties, and actions/handlers?
Since the properties are not in the signature, how does the action/handler know which properties were passed to it from the View/Razor Page?
What is the PageModel?
public class CreateModel : PageModel // what is this pagemodel, is it new or the same old model?
{
private readonly AppDbContext _db;
public CreateModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; } // why is the property outside?
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}
Razor pages is from my understanding pretty much a replacement for the old asp.net forms where you simply have a page with logic. A bit like how php does things.
If you create a page, let's say Pages/Index2.cshtml you should also create (or it may be created for you in Visual Studio) a "code-behind" file called Pages/Index2.cshtml.cs for example.
// The page file
#page
#using RazorPages
#model IndexModel2
<h2>Separate page model</h2>
<p>
#Model.Message
</p>
// The code-behind file
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
namespace RazorPages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
public void OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
}
You can still have models and initialize them in the code-behind file. But if you want controllers, I would suggest you do not use razor pages and just use the classical mvc. You can create a new project with it, just don't choose razor pages from the template. You certainly do not need to create a razor pages project. It is just an option. I personally do not really use it since I think is prone for one to repeat code since every code-behind file is just valid for one page afaik.
What is the PageModel?
A pagemodel is simply the code-behind file that does server side logic for your specific page.
I am not sure exactly what you are asking for, you bind models like any other razor page and properties in the code-behind class. The model is the code-behind file in my example.
how does the action/handler know which properties were passed to it from the View/Razor Page?
The action handler knows it by you specifying it in the razor page:
<input asp-for="Customer.Name" />
Please read more about Razor Pages here:
https://learn.microsoft.com/en-us/aspnet/core/mvc/razor-pages/?tabs=visual-studio
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.
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.
I’m working out the concepts for a new project where I need to support for multilingual URL’s. Ideally all URL’s need to be in the native language of the user. So we don’t want to use domain.com/en/contact and domain.com/es/contact but we like domain.com/contact and domain.com/contactar (contactar is Spanish for contact). Internally both should be routed to the same ContactController class.
This could be handled by adding multiple static routes to Global.asax.cs for each language but we’d like to make this very dynamic and would like the user of the system to be able to change the translation of the URL’s through the content management system. So we need some kind of dynamic mapping from URL’s to controllers and actions.
By looking at the source code of MVC3 I figured out that the ProcessRequestInit method of MvcHandler is responsible for determining which controller to create. It simply looks in the RouteData to get the name of the controller. One way to override the default MVC routing would be to create a simple default route that uses a custom RouteHandler. This RouteHandler forces MVC to use my own custom subclassed version of MvcHandler that overrides the ProcessRequestInit method. This overridden method insert my own dynamically found controller and action into the RouteData before calling back to the original ProcessRequestInit.
I’ve tried this:
Global.asax.cs
routes.Add(
new Route("{*url}", new MultilingualRouteHandler())
{
Defaults = new RouteValueDictionary(new { controller = "Default", action = "Default" })
}
);
MultilingualRouteHandler.cs
public class MultilingualRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MultilingualMVCHandler(requestContext);
}
}
MultilingualMvcHandler.cs
public class MultilingualMVCHandler : MvcHandler
{
public MultilingualMVCHandler(RequestContext context) : base(context)
{
}
protected override void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
{
if (RequestContext.RouteData.Values.ContainsKey("controller"))
{
RequestContext.RouteData.Values.Remove("controller");
}
if (RequestContext.RouteData.Values.ContainsKey("action"))
{
RequestContext.RouteData.Values.Remove("action");
}
RequestContext.RouteData.Values.Add("controller", "Product");
RequestContext.RouteData.Values.Add("action", "Index");
base.ProcessRequestInit(httpContext, out controller, out factory);
}
}
In this handler I hardcoded the controller and action for testing purposes to some fixed values but it’s not difficult to make this dynamic. It works but the only problem is that I had to modify the source code of ASP.NET MVC3 to get it working. The problem is that the ProcessRequestInit method of MvcHandler is private and thus cannot be overridden. I’ve modified the source code and changed it to protected virtual which allows me to override it.
This is all great but possibly not the best solution. It’s cumbersome that I would always need to distribute my own version of System.Web.Mvc.dll. It would be much better that it would work with the RTM version.
Am I missing any other possibilities of hooking into ASP.NET MVC that would allow me to dynamically determine the controller and action to launch, depending on the URL? One other way I thought of is to build the RouteCollection dynamically on *Application_Start* but I think that will make it more difficult to change it on the fly.
I would appreciate any tips of hooks that I’ve not yet found.
This is fairly old now, nut just in case anyone else is looking for something similar...
Unless I'm completely misunderstanding what you want to do, it's pretty simple really.
Step 1: Add a new route to global.ascx.cs containing a reference to your personal routing engine
routes.Add(new MyProject.Routing.ContentRoutingEngine());
Make sure that it is in the right place in the list of routes so that other routing engines can catch stuff before it if required, or continue the route search if your engine doesn't handle a particular route. I put it after the ignores, but before the MVC default routes.
Step 2: Create the Content Routing Engine, making sure that it inherites from System.Web.Routing.RouteBase abstract class, and overrides the GetRouteData and GetVirtualPath methods as required e.g.
public class ContentRoutingEngine : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeHandler = new MvcRouteHandler();
var currentRoute = new Route("{controller}/{action}", routeHandler);
var routeData = new RouteData(currentRoute, routeHandler);
// set your values dynamically here
routeData.Values["controller"] = "Home" ;
// or
routeData.Values.Add("action", "Index");
// return the route, or null to have it passed to the next routing engine in the list
return routeData;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
//implement this to return url's for routes, or null to just pass it on
return null;
}
}
and that should do it. You can change routes as dynamically as you wish within your engine, and no changes to MVC source required. Let the standard MVC RouteHandler actually invoke the controller.
Postscript: Obviously the code above is not production standard - it's written to make it as obvious as possible what's going on.
If you are allowing modification of urls through your CMS, then you will have to keep all old versions of the urls so that you can 301 redirect to the new ones.
The best bet for this will be to put the url tokens eg "contactar" in the db along with its corresponding controller.
query that, and create your routes out of that.
create a route that will handle the 301s
I think that most elegant solution would be using some action filter combined with custom ActionInvoker. That way, you could invoke an action that has specific filters applied. Something like ActionName attribute, only capable to accept multiple values (names).
Edit: Take a look at ActionMethodSelectorAttribute, meybe you don't need a custom ActionInvoker after all.
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.