3

My goal is to create an object to allow chaining of commands in MVC.Net views.

Here is an example use in a view of a menu I created using this concept:

<nav class="navigation">
    <%: Html
        .menu()
            .item("Introduction", "Introduction", "Home")
            .item("About", "About", "Home")
            .item("Systems", "Index", "Systems")
            /*.item("Categories", "Categories", "Health")*/
            .item("Test Cases", "TestCases", "Testing")
            .category("Logging")
                .item("UniMon Events", "UniMonEvents", "Logging")
            .end()
        .end() %>
</nav>

As you can see it allows for the quick construction of a multi-tiered menu with interdependencies between the various parts.

I would like to achieve this same effect for a form using lambda expressions.

The ideal syntax would look like this:

<%: Html
    .form()
        .hidden(m=>m.property1)
        .hidden(m=>m.property2)
    .end() %>

Where I am running into trouble is with the hidden method. It seems there is no way to get the compiler to infer m without passing it to the method hidden.

I can achieve this syntax:

<%: Html
    .form()
        .hidden(Html, m=>m.property1)
        .hidden(Html, m=>m.property2)
    .end() %>

Using this class and an extension method(not shown):

public class RouteForm
{
    public HtmlHelper HtmlHelper { get; private set; }
    public Dictionary<string, string> PostData { get; private set; }

    public RouteForm(HtmlHelper htmlHelper)
    {
        HtmlHelper = htmlHelper;
        PostData = new Dictionary<string, string>();
    }

    public RouteForm hidden<TModel, TValue>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
    {
        string name = ExpressionHelper.GetExpressionText(expression);
        string value = GetFieldValue(htmlHelper, expression);
        PostData.Add(name, value);
        return this;
    }
    private static string GetFieldValue<TModel, TValue>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
    {
        object oValue = expression.Compile()(htmlHelper.ViewData.Model);
        string value = (oValue is Enum) ? ((int)oValue).ToString() : oValue.ToString();
        return value; ;
    }
    public MvcHtmlString end()
    {
        //TODO: render form with post data
        return MvcHtmlString.Empty;
    }
}

I thought that perhaps a class with a generic type might be what I am looking for, so I tried this:

public class RouteForm<TModel>
{
    public HtmlHelper<TModel> HtmlHelper { get; private set; }
    public Dictionary<string, string> PostData { get; private set; }

    public RouteForm(HtmlHelper<TModel> htmlHelper)
    {
        HtmlHelper = htmlHelper;
        PostData = new Dictionary<string, string>();
    }

    public RouteForm<TModel> hidden<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
    {
        string name = ExpressionHelper.GetExpressionText(expression);
        string value = GetFieldValue(expression);
        PostData.Add(name, value);
        return this;//ERRORS: TModel is TModel
    }
    private string GetFieldValue<TModel, TValue>(Expression<Func<TModel, TValue>> expression)
    {
        object oValue = expression.Compile()(
            (TModel)HtmlHelper.ViewData.Model //ERRORS: Cannot convert type TModel to TModel
        );
        string value = (oValue is Enum) ? ((int)oValue).ToString() : oValue.ToString();
        return value; ;
    }
    public MvcHtmlString end()
    {
        //TODO: render form with post data
        return MvcHtmlString.Empty;
    }
}

I put the errors in the code above using comments.

Thanks!

0

3 Answers 3

3

You're using too many generic parameters.

Methods like GetFieldValue<TModel, ...> create a second TModel paramter which is not related to the first one.

In other words, they allow you to write

new RouteForm<PersonModel>().GetFieldValue<TruckModel, ...>()

This is obviously wrong.

Instead, just get rid of that parameter from each method and let them use the class' TModel parameter instead.

Sign up to request clarification or add additional context in comments.

3 Comments

Done. This is was quite obvious once you pointed it out. I can now write: new mvc.RouteForm<CurrentModel>(Html).hidden(m => m.Property);
Is there a way to get the compiler to infer the type from the constructor rather than explicitly passing it? Such as new mvc.RouteForm(Html).hidden(m => m.Property); Given the constructor: public RouteForm(HtmlHelper<TModel> htmlHelper)
@Nathaniel: No; type inference does not apply to constructors. Instead, use an extension method.
1

I guess the compilation error "ERRORS: TModel is TModel" is caused by declaring TModel twice in the generic declaration of hidden().

I haven't compiled this, but I'd try something like this:

public static class HtmlHelperExtensions
{
    public static RouteForm<TModel> form(this HtmlHelper helper, TModel model)
    {
        return new RouteForm<TModel>(helper);
    }
}

public class RouteForm<TModel>
{
    public RouteForm<TModel> hidden(Expression<Func<TModel, TValue>> expression)
    {
    }        
    public MvcHtmlString end()
    {
    }
}

Comments

0

Thanks to both of you I was able to create a class that achieves the syntax I was looking for.

(the class is simplified for this post) class:

public class RouteForm<TModel>
{
    public HtmlHelper<TModel> HtmlHelper { get; private set; }

    public RouteForm(HtmlHelper<TModel> htmlHelper)
    {
        HtmlHelper = htmlHelper;
    }

    public RouteForm<TModel> hidden<TValue>(Expression<Func<TModel, TValue>> expression)
    {
        return this;
    }
    public MvcHtmlString end()
    {
        return MvcHtmlString.Empty;
    }
}

extension method:

public static RouteForm<TModel> form<TModel>(this HtmlHelper<TModel> helper)
{
     return new RouteForm<TModel>(helper);
}

markup syntax:

<%: Html
    .form()
        .hidden(m=>m.Property1)
        .hidden(m=>m.Property2)
    .end()
%>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.