views:

1085

answers:

7

I would like to generate some JavaScript on the server side in ASP.Net MVC. Is there a view engine that supports this? Ideally I would like to be able to get JavaScript from an url like:

http://myapp/controller/action.js

I've looked at the MonoRail project, and they seem to have this feature, but it's very lacking in documentation, and I can't find any ports to ASP.Net MVC.

Edit: The idea is to be able to render a page both as standard HTML by using a url like:

http://myapp/controller/action

and as js (specifically an ExtJS component) by using the first url in the question. There would be only a single action in the controller, but two views: one for HTML and one for JS.

Edit 2: I basically wanted to achieve the same result as router extension parsing/request handling in CakePHP.

+1  A: 

I had to do something very similar, and I used the nvelocity view engine from MVCContrib - technically you could use the default aspx view engine, but I found that the nvelocity syntax was a lot more straightforward for puting out script (don't worry if you haven't used it before - I spent about 10 minutes figuring it out!).

Then you just need to add a route to the route table to handle directing your .js url to the action!

EDIT

Can't verify this as I don't have visual studio to hand but for the route, you might have something like this:

RouteTable.Routes.Add(new Route
{
   Url = "[controller]/[action].js",
   Defaults = new { controller="home", requestType="javascript" }, // Whatever...
   RouteHandler = typeof(MvcRouteHandler)
});

RouteTable.Routes.Add(new Route
{
   Url = "[controller]/[action]",
   Defaults = new { controller="home"}, // Whatever...
   RouteHandler = typeof(MvcRouteHandler)
});

Requests ending in .js should go through the first route - extensionless requests fall through to the second.

Then your action could have a requestType param:

public ActionResult MyAction (RequestType requestType)
{
  if(requestType == RequestType.JavaScript)
  {
     ... new nvelocity view to render javascript
  }
  else
  {
     ... 
  }
}

As for directory structure - you're on your own with that! Not because I don't want to be helpful, but more down to the fact that you have flexibility to do what you want with it!

Jennifer
Could you perhaps give an example of the route I could use for this, as well as a possible directory structure?
Jacob
That is a great idea. I basically abstracted it down to the viewengine level to make it transparent to my controllers.
Jacob
A: 

I can't see how you need it to be a specific .js-file. You could just point the javascript-link to an aspx page: mysite/controller/js-generator and have the aspx-page spit out the js for ya. Which will be the same as what you want.

Filip Ekberg
When you have complete control over the url, sometimes it's nice to make it a bit tidy/conventional by putting the .js into the URL if you are returning javascript...
Jennifer
Of course it is, but thats just about the extension.
Filip Ekberg
+1  A: 

You don't necessarily need view engine for that, just return plain text result.

Here is a controller (MonoRail) I've used to render some user culture settings in a javascript object:

[ControllerDetails("js")]
public class JavascriptController : Controller
{
 private ISessionContext sessionContext;

 public JavascriptController(ISessionContext sessionContext)
 {
  this.sessionContext = sessionContext;
 }

 public void CultureInfo()
 {
  var scriptformat = @"var cultureInfo = {0};";
  var json = Context.Services.JSONSerializer.Serialize(getJSONiZableCultureInfo(sessionContext.CurrentCulture));
  RenderText(String.Format(scriptformat, json));
 }

 object getJSONiZableCultureInfo(System.Globalization.CultureInfo  culture)
 {
  return new
      { // add more there
       culture.NumberFormat
      };
 }
}

for more complex things for which raw text rendering would smell, any view engine would work.

Also you are not forced to use .js extension to put the url in your script tag.

smoothdeveloper
About the extension: I know, but it's rather nice to use (you immediately know the type of content you're getting). See http://tryingthisagain.com/2007/12/14/aspnet-mvc-routing-limitations/
Jacob
I missed the point of your question (now clearer with the edit).Seems like you are talking about some javascript helper.Again, that is achieved with plain helper class which define a somewhat DSL to output javascriptfor monorail take at this, see:http://markmail.org/message/zzjxhpjwxsnib2uk
smoothdeveloper
That is the part of monorail that i referred to. I was looking for something that would handle JS views in ASP like the .njs views in monorail.
Jacob
A: 

If you just want to generate a javascript based on the ViewData you can create your own custom result. Here is an example.

Eduardo Campañó
+2  A: 

Based on your edit I'll try with a new answer asumming you need json data for ExtJS. I've just tested it in the app I'm building and it works fine. First you need two routes

{controller}/{action}.{format}

{controller}/{action}

Now the Controller class has a Json method to serialize whatever object you want and it's a JsonResult so you can just use:

public ActionResult List(string format) {

    // logic here

    if (string.IsNullOrEmpty(format)) {
        return View();
    } else if (format == "js") {
        return Json(object_to_serialize);
    }

}
Eduardo Campañó
This is more or less what I wanted to do, yes... However, I prefer to keep view decisions out of my controllers, so I made a custom view engine wrapper thingy.
Jacob
A: 

I wanted to extend this idea to not only allow Javascript views, but more or less any type of document. To use it, you just put the views for *.js urls in a subfolder of your controller's view folder:

\Views
+-\MyController
  +-\js
  | +-Index.aspx <- This view will get rendered if you request /MyController/Index.js
  +-Index.aspx

The class is a decorator for any type of ViewEngine, so you can use it with NVelocity/WebForms/Whatever:

public class TypeViewEngine<T> : IViewEngine where T : IViewEngine
{
    private readonly T baseEngine;
    public T BaseEngine
    {
        get { return baseEngine; }
    }

    public TypeViewEngine(T baseEngine)
    {
        this.baseEngine = baseEngine;
    }

    public void RegisterRoutes(RouteCollection routes)
    {
        routes.MapRoute(
            "TypeViewEngine",
            "{controller}/{action}.{type}",
            new {controller = "Home", action = "Index", type = "html"}
            );
    }

    public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
    {
        var vars = controllerContext.RouteData.Values;
        if(vars["type"] != null && vars["type"].ToString() != "html")
        {
            viewName = string.Format("{0}/{1}", vars["type"], viewName);
        }
        return baseEngine.FindView(controllerContext, viewName, masterName);
    }

    public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
    {
        return baseEngine.FindPartialView(controllerContext, partialViewName);
    }

    public void ReleaseView(ControllerContext controllerContext, IView view)
    {
        baseEngine.ReleaseView(controllerContext, view);
    }
}

Then, in your Global.asax.cs file:

protected void Application_Start()
{
    var ve = new TypeViewEngine<WebFormViewEngine>(new WebFormViewEngine());
    ve.RegisterRoutes(RouteTable.Routes);
    RegisterRoutes(RouteTable.Routes);

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(ve);
}

Thanks for everyone's help with this!

Jacob
+2  A: 

The July 2007 ASP.NET Futures release has the new Managed JScript (I can't find a newer one). I've successfully batted it around a bit - but beware, you will probably be forced to do some extra work, the .ASPX parsing for JScript is unusably buggy.

http://www.microsoft.com/downloads/details.aspx?FamilyId=A5189BCB-EF81-4C12-9733-E294D13A58E6&amp;displaylang=en

Dan Finch