Reputation: 546
POST EDITED BELOW
We can't figure out why UrlHelper
is returning null strings when used from the WebApi controller's context.
We've done the neccesary debugging but we can't find out why this happens, the RouteData has the routes in it but it doesn't seem to be working.
For the most part, we use a RenderViewToString function, which loads views that consist of calls to Url.RouteUrl(routeName)
.
Something that's been tried is creating a custom UrlHelper (but to no avail) and debugging with either UrlHelper's (MVC / HTTP).
Attribute routing is used everywhere with route names.
Example usage code:
public class WebApiController : BaseApiController
{
[HttpPost]
[ResponseType(typeof(string))]
[Route("cart/get/checkout", Name = "api.cart.get.checkout")]
public IHttpActionResult GetCheckOutShoppingCart([FromBody] string data)
{
return Ok(RenderViewToString("CartController", "_CheckOutCartPartial", new ShoppingCartModel(Auth.IsAuthenticated ? Auth.GetCustomer().DefaultShippingInfo.CountryId : 148)
{
AddInsurance = false,
InsuredShipping = insuredShipping,
CurrentDeliveryMethodId = deliveryMethodId,
CurrentPaymentMethodId = paymentMethodId
}));
}
}
BaseApiController class:
public class BaseApiController : ApiController
{
public static string RenderViewToString(string controllerName, string viewName)
{
return RenderViewToString(controllerName, viewName, new Dictionary<string, object>());
}
public static string RenderViewToString(string controllerName, string viewName, object model)
{
using (var writer = new StringWriter())
{
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext =
new ControllerContext(
new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null),
new HttpResponse(null))), routeData, new FakeController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View,
new ViewDataDictionary(model), new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
public static string RenderViewToString(string controllerName, string viewName, Dictionary<string, Object> data)
{
using (var writer = new StringWriter())
{
var viewData = new ViewDataDictionary();
foreach (var kv in data)
{
viewData[kv.Key] = kv.Value;
}
var routeData = new RouteData();
routeData.Values.Add("controller", controllerName);
var fakeControllerContext =
new ControllerContext(
new HttpContextWrapper(new HttpContext(new HttpRequest(null, "http://google.com", null),
new HttpResponse(null))), routeData, new FakeController());
var razorViewEngine = new RazorViewEngine();
var razorViewResult = razorViewEngine.FindView(fakeControllerContext, viewName, "", false);
var viewContext = new ViewContext(fakeControllerContext, razorViewResult.View, viewData,
new TempDataDictionary(), writer);
razorViewResult.View.Render(viewContext, writer);
return writer.ToString();
}
}
private class FakeController : ControllerBase
{
protected override void ExecuteCore()
{
}
}
}
EDIT
We've put together a class that should in theory work, but it doesn't. The RouteData has both the MVC and API routes in the collection.
public static class Url
{
public static bool IsWebApiRequest()
{
return
HttpContext.Current.Request.RequestContext.HttpContext.CurrentHandler is
System.Web.Http.WebHost.HttpControllerHandler;
}
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
if (IsWebApiRequest())
{
var helper = new System.Web.Http.Routing.UrlHelper();
url = helper.Link(routeName, routeValues);
}
else
{
var helper = new System.Web.Mvc.UrlHelper();
url = helper.RouteUrl(routeName, routeValues);
}
return url;
}
catch
{
return url;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
if (IsWebApiRequest())
{
var helper = new System.Web.Http.Routing.UrlHelper();
url = helper.Link(routeName, routeValues);
}
else
{
var helper = new System.Web.Mvc.UrlHelper();
url = helper.HttpRouteUrl(routeName, routeValues);
}
return url;
}
catch
{
return url;
}
}
}
Upvotes: 5
Views: 4511
Reputation: 546
This issue has been resolved by building a custom UrlHelper class, which looks into the RouteTable and then returns the url with the patterns replaced.
public static class Link
{
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var route = (Route)RouteTable.Routes[routeName];
if (route == null)
return url;
url = "~/".AbsoluteUrl() + route.Url;
url = url.Replace("{culture}", System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.ToLower());
if (routeValues == null)
return url;
var values = routeValues.GetType().GetProperties();
Array.ForEach(values, pi => url = Regex.Replace(url, "{" + pi.Name + "}", pi.GetValue(routeValues, null).ToString()));
return url;
}
catch
{
var newUrl = RouteUrl("403");
if(newUrl == String.Empty)
throw;
return newUrl;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
return RouteUrl(routeName, routeValues);
}
}
Upvotes: 3
Reputation: 1480
OK, based on your comment, it seems that you're trying to call your Url.RouteUrl(string routeName, object routeValues = null)
or Url.HttpRouteUrl(string routeName, object routeValues = null)
from your MVC layout.cshtml file.
If that's the case, the Url.IsWebApiRequest()
would return false
because the layout.cshtml file is only processed as part of handling MVC request and not WebAPI. That will cause the url generation methods to use MVC route collection instead of the WebAPI collection.
Change your Url
class so that RouteUrl
always builds WebAPI url and HttpRouteUrl
only builds MVC url then in your layout file use proper method based on which kind of url you need in the particular context.
public static class Url
{
public static bool IsWebApiRequest()
{
return
HttpContext.Current.Request.RequestContext.HttpContext.CurrentHandler is
System.Web.Http.WebHost.HttpControllerHandler;
}
public static string RouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var helper = new System.Web.Http.Routing.UrlHelper();
return helper.Link(routeName, routeValues);
}
catch
{
return url;
}
}
public static string HttpRouteUrl(string routeName, object routeValues = null)
{
var url = String.Empty;
try
{
var helper = new System.Web.Mvc.UrlHelper();
return helper.HttpRouteUrl(routeName, routeValues);
}
catch
{
return url;
}
}
}
Upvotes: 1
Reputation:
WebApi is not MVC. Even though it looks very similar, it's a completely different system.
UrlHelper (which is MVC) will be looking at the MVC route table, and ignore any WebApi routes.
Try creating the route the hard way, essentially hard-coding the controller and action names into the views. :-(
Upvotes: 1
Reputation: 1893
Try Uri.Link(routeName, object)
var uri = Url.Link("api.cart.get.checkout", new { id = 1 });
This will inject the given object's properties into the route if the property name matches a route parameter.
Upvotes: 2