0
votes

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
2
I don't understand: you want to throw an exception everytime someone calls a default helper and yet you do not want to rewrite all default helpers with your custom ones. How do you expect this to happen? - Darin Dimitrov
@DarinDimitrov: I want to provide a custom BeginForm helper. Let's say it will be called SecureBeginForm. But not other helpers... So whenever one would use i.e. Html.EditorFor everything would be fine, but if they used BeginForm it should throw an exception or not be available to execute... Get it? - Robert Koritnik
@DarinDimitrov: I've edited my question to include an example that explains the behaviour using simplified code example. Hope it helps. - Robert Koritnik
Yes it helps. I understood what you are trying to achieve. - Darin Dimitrov
Imo don't bother... All they are is helpers... Someone who knows MVC's naming pattern can just make a manual form element from scratch with the right action url for the controller and the right names on the fields, and their form will work. You can't stop them from using standard html elements. - Ryan Mann

2 Answers

1
votes

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

enter image description here

or a runtime exception if you haven't.

0
votes

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.