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?

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254

3 Answers3

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.

Community
  • 1
  • 1
Mike Bailey
  • 12,479
  • 14
  • 66
  • 123
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

Martin Vich
  • 1,062
  • 1
  • 7
  • 22
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.

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254