user610217
user610217

Reputation:

MVC5 Attribute Routing in WebApi space

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?

Upvotes: 2

Views: 590

Answers (3)

user610217
user610217

Reputation:

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.

Upvotes: 0

Mike Bailey
Mike Bailey

Reputation: 12817

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.

Upvotes: 1

Martin Vich
Martin Vich

Reputation: 1082

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

Upvotes: 0

Related Questions