2

I am using an MVC controller to create a dynamic JavaScript file (I am not happy with that part, but I'm trying to maintain compatibility with a previous version) in a Web Api project...

The client is consuming a URL like:

~/api/assessments/{id:int}.js?locale={locale?}

I created an MVC JavaScriptController and added a method like this:

    [Route("~/api/data/{id:int}.js")]
    public async Task<PartialViewResult> GetData(int id, string locale)
    {
        try
        {
            Response.ContentType = "application/javascript";
            ViewBag.Locale = locale ?? "en";
            var model = await this._dataService.GetData(id.ToString());
            return PartialView(model);
        }
        catch (Exception ex)
        {
            this._logger.Error("Task<PartialViewResult> GetData(int, string)", ex);
            return PartialView("JavaScriptError", SelectException(ex));
        }
    }

When I try to invoke this call, however, I get a 404:

<Error>
    <Message>
        No HTTP resource was found that matches the request URI 'http://.../api/data/29.js?locale=en'.
    </Message>
    <MessageDetail>
        No type was found that matches the controller named 'data'.
    </MessageDetail>
</Error>

I'm thinking that the WebApi "~/api" prefix is stepping on the MVC 5 route, although I suppose it could be something completely different. How can I render this MVC view at the specified URL?

4
  • What is the namespace of the Route attribute? Commented Apr 15, 2015 at 16:25
  • I did not specify one. Commented Apr 15, 2015 at 16:28
  • Make sure it's System.Web.Http.RouteAttribute and not System.Web.Mvc.RouteAttribute. Commented Apr 15, 2015 at 16:31
  • @haim770, that does not seem to make a difference. Commented Apr 15, 2015 at 16:33

3 Answers 3

1

You don't need to include the query string parameters or the relative path as part of your route:

    [Route("api/data/{id:int}.js")]
    protected async Task<PartialViewResult> GetData(int id, string locale)
    {
        try
        {
            Response.ContentType = "application/javascript";
            ViewBag.Locale = locale ?? "en";
            var model = await this._dataService.GetData(id.ToString());
            return PartialView(model);
        }
        catch (Exception ex)
        {
            this._logger.Error("Task<PartialViewResult> GetData(int, string)", ex);
            return PartialView("JavaScriptError", SelectException(ex));
        }
    }

Since only a specific part of your API is actually templated (the name of the "file") you only need to include that as part of the route.

However, you'll still run into issues where .NET will try to treat the request for a .js as being a request for a file.

For this to work, you'll likely have to enable the relaxed filesystem mapping:

<httpRuntime relaxedUrlToFileSystemMapping="true" />

However, you do need to be aware of security implications of enabling this. You'll be opening yourself up to potential attacks where remote users can access files on your filesystem if you don't set up NTFS permissions correctly.

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

1 Comment

Hmm, makes sense with the inputs, but this does not seem to make a difference.
0

Do you call MapHttpAttributeRoutes() during your route registration?

See http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#why

Comments

0

Well, it appears I was correct; the WebApi routing mechanism steps on the MVC attribute routing. Ultimately, I had to commment out the following line in my WebApiConfig.cs file:

//config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});

After that, the MVC routing worked as expected.

Comments

Your Answer

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