5

Actually, I already have at least 3 different solutions for the problem, I just don't like any of them for various reasons.

In ASP.NET MVC/Razor, there are no controls anymore, in the sense as they were in ASP.NET. An ASCX control used to be view+controller intertwined, so I get it. Instead we have html helpers that emit strings, html fragments. Let's say I want to create a helper method in a library that creates a jQuery UI date picker.

First solution. I just write a method that first creates a textbox (actually I can just call TextBoxFor because it does the same thing for me), then it creates a small JavaScript code fragment that calls $('#textboxid').datepicker(). Then I can call that in my Razor views and it works. I don't like the fact that view-related stuff is done in code, and not in Razor, but all default editor templates are like that, sadly. I don't like the fact that there will be lots of small script fragments after each textbox in the output html. And my code fragment can get complex, if that initializer JS had many parameters, it would look ugly, there would be string escape issues, etc.

Microsoft is pushing unobtrusive JavaScript architecture. I could just put a CSS marker class on my textbox, add some data-attributes if needed, and emit only one JavaScript that turns all my textboxes to date pickers, based on the CSS marker class. This one last part is the main problem, I do need an imperative JS code to do that. Unobtrusive AJAX and validation work well because they don't need that at all, they just respond to user interaction events. This is not the case, I need to turn my textboxes to date pickers whenever they appear. And they can appear dynamically in an editable grid, or after an AJAX call, whenever.

Third solution, view should be done in Razor, so let's do it in Razor, as an editor template. Problem is, we're talking about a class library here. In the past, I have seen ugly hacks to embed .cshtml files as embedded resources or compile them to actual C# code during compilation. Last I checked, they seemed like ugly hacks and weren't supported.

Is there another solution? If not, which of the above is the nicest, the one that most conforms to the state of the art coding conventions, or Microsoft's idea, etc?

  • What prevents you from using or creating jQuery plugin controls, or some similar mechanism? There are other ways to get controls besides emitting HTML and Javascript from the server. – Robert Harvey Jan 14 '14 at 18:02
  • Can't you just put the one line of javascript to turn all the marked textboxes into date pickers into a global javascript file and/or one place on the base page? If it has nothing to act on, it won't do anything. – Bobson Jan 14 '14 at 21:34
5

I'm not entirely sure I've identified the right problem, but if I have, this may help:

We created a class called ScriptManager which lets us add arbitrary javascript files and/or functions to the bottom of all our pages when they are rendered. Give this a try:

public class ScriptManager
{
    public List<string> scripts = new List<string>();
    public List<string> scriptFiles = new List<string>();
}
public static class HtmlExtensions
{

    [ThreadStatic]
    private static ControllerBase pageDataController;
    [ThreadStatic]
    private static ScriptManager pageData;

    public static ScriptManager ScriptManager(this HtmlHelper html)
    {
        ControllerBase controller = html.ViewContext.Controller;

        // This makes sure we get the top-most controller
        while (controller.ControllerContext.IsChildAction)
        {
            controller = controller.ControllerContext.ParentActionViewContext.Controller;
        }
        if (pageDataController == controller)
        {
            // We've been here before
            return pageData;
        }
        else
        {
            // Initial setup
            pageDataController = controller;
            pageData = new ScriptManager();
            return pageData;
        }
    }
}

Then, at the bottom of your _Layout.cshtml page:

@foreach (var script in Html.ScriptManager().scriptFiles.Distinct())
{
    @Html.Script(script);
}
@foreach (var script in Html.ScriptManager().scripts)
{
    @Html.Raw("<script type='text/javascript'>")
    @Html.Raw(script)
    @Html.Raw("</script>")
}

Finally, you use it in a partial view like:

@{
  Html.ScriptManager().scriptFiles.Add(Url.Content("~/Scripts/jquery.blockUI.js"));
 }

You lose the ability to tell the client about the scripts in the header of the page, but they will be included on all pages you use a partial view that uses the blockUI (for example).

0

Another one is to have a cshtml and corresponding controller beneath. Such a thing we call a control and embed it in another view using @Html.RenderPartial('<action_url>', <model>). In the controller action should be annotated with [ChildActionOnly] attribute so it cannot be called in a direct call.

Not that I am huge fan of that approach, just describe it here so we have full picture.

You can also use Razor's @helper in addition to you @Html. helpers.

  • In my solution #3, I meant an editor template (edited my question). This is similar, and suffers from the same problem, not trivial to put it outside your main project, into a class library. – fejesjoco Jan 14 '14 at 20:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.