Is it possible to get a URL from an action without knowing ViewContext (e.g., in a controller)? Something like this:
LinkBuilder.BuildUrlFromExpression(ViewContext context, Expression<Action<T>> action)
...but using Controller.RouteData instead of ViewContext. I seem to have metal block on this.
Here's how I do it in a unit test:
private string RouteValueDictionaryToUrl(RouteValueDictionary rvd)
{
var context = MvcMockHelpers.FakeHttpContext("~/");
// _routes is a RouteCollection
var vpd = _routes.GetVirtualPath(
new RequestContext(context, _
routes.GetRouteData(context)), rvd);
return vpd.VirtualPath;
}
Per comments, I'll adapt to a controller:
string path = RouteTable.Routes.GetVirtualPath(
new RequestContext(HttpContext,
RouteTable.Routes.GetRouteData(HttpContext)),
new RouteValueDictionary(
new { controller = "Foo",
action = "Bar" })).VirtualPath;
Replace "Foo" and "Bar" with real names. This is off the top of my head, so I can't guarantee that it's the most efficient solution possible, but it should get you on the right track.
Craig, Thanks for the correct answer. It works great, and it also go me thinking. So in my drive to eliminate those refactor-resistent "magic strings" I have developed a variation on your solution:
public static string GetUrlFor<T>(this HttpContextBase c, Expression<Func<T, object>> action)
where T : Controller
{
return RouteTable.Routes.GetVirtualPath(
new RequestContext(c, RouteTable.Routes.GetRouteData(c)),
GetRouteValuesFor(action)).VirtualPath;
}
public static RouteValueDictionary GetRouteValuesFor<T>(Expression<Func<T, object>> action)
where T : Controller
{
var methodCallExpresion = ((MethodCallExpression) action.Body);
var controllerTypeName = methodCallExpresion.Object.Type.Name;
var routeValues = new RouteValueDictionary(new
{
controller = controllerTypeName.Remove(controllerTypeName.LastIndexOf("Controller")),
action = methodCallExpresion.Method.Name
});
var methodParameters = methodCallExpresion.Method.GetParameters();
for (var i = 0; i < methodParameters.Length; i++)
{
var value = Expression.Lambda(methodCallExpresion.Arguments[i]).Compile().DynamicInvoke();
var name = methodParameters[i].Name;
routeValues.Add(name, value);
}
return routeValues;
}
I know what some will say...dreaded reflection! In my particular application, I think the benefit of maintainability outweighs performance conerns. I welcome any feedback on this idea and the code.
Related
I have a working OData controller, which supports all the normal get/put etc.
What I want to do is pass a normal odata $filter string from the client, parse and execute the filter on the server and run some code on the resulting IEnumerable.
I've messed around with ODataQueryContext, ODataQueryOptions, FilterQueryOption etc, but not really got anywhere.
Does anyone have any working examples?
Edit: I've added my function skeleton, just need to fill in the blanks
public HttpResponseMessage GetJobs(string filter)
{
*** How to convert the filter into IQueryable<Job> ***
var queryable = ?????
var settings = new ODataQuerySettings();
var jobs = queryOptions.ApplyTo(querable, settings) as IQueryable<Job>;
CsvSerializer csvSerializer = new CsvSerializer();
string csv = csvSerializer.Serialise(jobs);
string fileName = string.Format("{0} Jobs.csv", filter);
return CreateCsvResponseMessage(csv, fileName);
}
I recently had a scenario where I needed this sort of feature as well. This is what I came up with.
private static IQueryable<T> ApplyODataFilter<T>(IQueryable<T> data, string filterString) where T : class
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<T>(typeof(T).Name);
ODataQueryContext context = new ODataQueryContext(builder.GetEdmModel(), typeof(T), new ODataPath());
ODataQueryOptionParser queryOptionParser = new ODataQueryOptionParser(
context.Model,
context.ElementType,
context.NavigationSource,
new Dictionary<string, string> { { "$filter", filterString } });
FilterQueryOption filter = new FilterQueryOption(filterString, context, queryOptionParser);
IQueryable query2 = filter.ApplyTo(data, new ODataQuerySettings());
return query2.Cast<T>();
}
Try using OData code generator to generate client side code. you can following the following blog:
How to use OData Client Code Generator to generate client-side proxy class
The for the filter, the following is an example:
var q2 = TestClientContext.CreateQuery<Type>("Accounts").Where(acct => acct.Birthday > new DateTimeOffset(new DateTime(2013, 10, 1)));
There are some sample code in the codeplex to show how to do query.
Check this:
https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/v3/ODataQueryableSample/Program.cs
Update:
There is some sample code in the controller of the sample I gave you.
Write your code as below:
public IQueryable<Order> Get(ODataQueryOptions queryOptions)
{
if (queryOptions.Filter != null)
{
var settings = new ODataQuerySettings();
var filterResult = queryOptions.ApplyTo(OrderList.AsQueryable(), settings) as IQueryable<Order>;
// Use the filter result here.
}
}
Update 2:
You can get the raw string of the filter from ODataQueryOptions.
public IQueryable<Order> Get(ODataQueryOptions queryOptions)
{
string filterString = queryOptions.Filter.RawValue;
// Use the filterString
}
Update 3:
(Note: ODataProperties is an extension method in static class
System.Web.Http.OData.Extensions.HttpRequestMessageExtensions)
public HttpResponseMessage GetJobs(string filter)
{
var context = new ODataQueryContext(Request.ODataProperties().Model, typeof(Job));
var filterQueryOption = new FilterQueryOption(filter, context);
IQueryable<Job> queryable = GetAllJobs();
var settings = new ODataQuerySettings();
var jobs = filterQueryOption.ApplyTo(queryable, settings) as IQueryable<Job>;
CsvSerializer csvSerializer = new CsvSerializer();
string csv = csvSerializer.Serialise(jobs);
string fileName = string.Format("{0} Jobs.csv", filter);
return CreateCsvResponseMessage(csv, fileName);
}
I previously had a Web API controller that looked like this:
public IQueryable<ApiDesignOverview> GetList(
string brandIds = "",
string categoryIds = "",
string query = "",
string categoryOp = "or")
I heard that the OData NuGet package now supports the $inlinecount OData parameter, so I tried to add it using the instructions from http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options - I don't want to have to use OData wholesale as that would entail a large amount of re-architecturing of the app, so I went for the PageResult<T> option.
So now my controller looks like this:
public PageResult<ApiDesignOverview> GetList(
ODataQueryOptions<ApiDesignOverview> options,
string brandIds = "",
string categoryIds = "",
string query = "",
string categoryOp = "or")
My problems are now:
How do I mock a ODataQueryOptions for unit testing?
If they can't be mocked, how do I create one? I need a ODataQueryContext to construct one, which requires a Microsoft.Data.Edm.IEdmModel, which requires... what? I can't find any documentation for this.
Really, it would be better if I could remove the ODataQueryOptions from the controller signature like before. Is this possible?
If you do not (or cannot as in my case) want to change away from using ODataQueryOptions and PageResult, here is how you can create an ODataQueryOptions instance for unit tests:
//arrange
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/MyProject/api/Customers?$filter=CustomerID eq 1");
var controller = new CustomersController
{
Request = request
};
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
var opts = new ODataQueryOptions<Customer>(new ODataQueryContext(modelBuilder.GetEdmModel(),typeof(Customer)), request);
//act
var result = controller.Get(opts);
//assert
Assert.AreEqual(1, result.Items.First().CustomerID);
If you prefer returning IQueryable and yet want support for $inlinecount, it is still possible to do that by modyifying QueryableAttribute.
public class InlineCountQueryableAttribute : QueryableAttribute
{
private static MethodInfo _createPageResult =
typeof(InlineCountQueryableAttribute)
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(m => m.Name == "CreatePageResult");
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
HttpRequestMessage request = actionExecutedContext.Request;
HttpResponseMessage response = actionExecutedContext.Response;
IQueryable result;
if (response.IsSuccessStatusCode
&& response.TryGetContentValue<IQueryable>(out result))
{
long? inlineCount = request.GetInlineCount();
if (inlineCount != null)
{
actionExecutedContext.Response = _createPageResult.MakeGenericMethod(result.ElementType).Invoke(
null, new object[] { request, request.GetInlineCount(), request.GetNextPageLink(), result }) as HttpResponseMessage;
}
}
}
internal static HttpResponseMessage CreatePageResult<T>(HttpRequestMessage request, long? count, Uri nextpageLink, IEnumerable<T> results)
{
return request.CreateResponse(HttpStatusCode.OK, new PageResult<T>(results, nextpageLink, count));
}
}
Notice, that I am using reflection to create PageResult. You can instead return an object of your liking that can be formatted by the formatter that you use. An anonymous object with results and count will work too if you are using the Json formatter.
In the latest ODataController there is an AllowedQueryOptions that solves this.
public class MyOdataController : ODataController
{
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Product> Get()
{
return Products.AsQueryable();
}
}
I have a list of URLs that I obtained by querying Google Analytics data. I want to run each of these URLs through the MVC pipeline to get the ActionResult. The action result contains the view model from which I can extract some important information.
Based on the extensibility of MVC, I thought this would be easy. I thought I could mock up a HttpRequest using the string URL and pass it through the routing and controller. My end point would be invoking the action method which would return the ActionResult. I'm finding bits and pieces of what I need, but a lot of the methods are protected within the various classes and the documentation on them is pretty sparse.
I somehow want to reach in to the ControllerActionInvoker and get the result of the call to the protected function InvokeActionMethod.
First of all, Darin's answer got me started, but there's a lot more detail to the final solution, so I'm adding a separate answer. This one is complex, so bear with me.
There are 4 steps to getting the ViewResult from a URL:
Mock the RequestContext via the routing system (Darin's answer got me started on this).
Uri uri = new Uri(MyStringUrl);
var request = new HttpRequest(null, uri.Scheme + "://" + uri.Authority + uri.AbsolutePath, string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1));
var response = new HttpResponse(new StringWriter());
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
// We shouldn't have to do this, but the way we are mocking the request doesn't seem to pass the querystring data through to the route data.
foreach (string key in request.QueryString.Keys)
{
if (!routeData.Values.ContainsKey(key))
{
routeData.Values.Add(key, request.QueryString[key]);
}
}
var requestContext = new System.Web.Routing.RequestContext(contextBase, routeData);
Subclass your controller. Add a public method that allows you to call the protected Execute(RequestContext) method.
public void MyExecute(System.Web.Routing.RequestContext requestContext)
{
this.Execute(requestContext);
}
In the same subclassed controller, Add a public event that hooks in to the protected OnActionExecuted event. This allows you to reach in a grab the ViewResult via the ActionExecutedContext.
public delegate void MyActionExecutedHandler(ActionExecutedContext filterContext);
public event MyActionExecutedHandler MyActionExecuted;
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (MyActionExecuted != null)
{
MyActionExecuted(filterContext);
}
}
Tie everything together by instantiating an instance of the new controller subclass, adding an event handler, and calling the new public execute method (passing in the mocked RequestContext). The event handler will give you access to the ViewResult.
using (MyCompany.Controllers.MyController c = new Controllers.MyController())
{
c.MyActionExecuted += GrabActionResult;
try
{
c.MyExecute(requestContext);
}
catch (Exception)
{
// Handle an exception.
}
}
and here's the event handler:
private void GrabActionResult(System.Web.Mvc.ActionExecutedContext context)
{
if (context.Result.GetType() == typeof(ViewResult))
{
ViewResult result = context.Result as ViewResult;
}
else if (context.Result.GetType() == typeof(RedirectToRouteResult))
{
// Handle.
}
else if (context.Result.GetType() == typeof(HttpNotFoundResult))
{
// Handle.
}
else
{
// Handle.
}
}
The difficulty here consists into parsing the url into its constituent controller and action. Here's how this could be done:
var url = "http://example.com/Home/Index";
var request = new HttpRequest(null, url, "");
var response = new HttpResponse(new StringWriter.Null);
var context = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
var values = routeData.Values;
var controller = values["controller"];
var action = values["action"];
Now that you know the controller and the action you could use reflection to instantiate and execute it.
Try this:
object result = null;
Type controller = Type.GetType("MvcApplication4.Controllers.HomeController");
if (controller != null)
{
object controllerObj = Activator.CreateInstance(controller, null);
if (controller.GetMethod("ActionName") != null)
{
result = ((ViewResult)controller.GetMethod("ActionName").Invoke(controllerObj, null)).ViewData.Model;
}
}
I assumed normal routes are configured in the application and can be retrieved using regex or string operations. Following your discussion, I learned that you guys want to really follow through the MVC pipeline by digging into the framework by not using reflection or any hardcording techniques. However, I tried to search to minimize hardcoding by trying to match the url with the routes configured in the application by following this thread
How to determine if an arbitrary URL matches a defined route
Also, I came across other thread which creates httprequest to access routedata object but again reflection needs to be used for this.
String URL to RouteValueDictionary
Thanks Ben Mills, this got me started with my own problem. However I found that I didn't have to do 2, 3 or 4, by doing the following.
Uri uri = new Uri(MyStringUrl);
var absoluteUri = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath;
var query = string.IsNullOrWhiteSpace(uri.Query) ? null : uri.Query.Substring(1);
var request = new HttpRequest(null, absoluteUri, query);
Getting access to the string writer is important.
var sw = new StringWriter();
var response = new HttpResponse(sw);
var context = new HttpContext(request, response);
var contextBase = new HttpContextWrapper(context);
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
If we assign the RouteData to the request context we can use the MVC pipeline as intended.
request.RequestContext.RouteData = routeData;
var controllerName = routeData.GetRequiredString("controller");
var factory = ControllerBuilder.Current.GetControllerFactory();
var contoller = factory.CreateController(request.RequestContext, controllerName);
controller.Execute(request.RequestContext);
var viewResult = sw.ToString(); // this is our view result.
factory.ReleaseController(controller);
sw.Dispose();
I hope this helps someone else wanting to achieve similar things.
I'm building a website in MVC 4 & using Automapper to map from domain objects to Viewmodel objects. I have injected Automapper as stated here http://rical.blogspot.in/2012/06/mocking-automapper-in-unit-testing.html
and it's working fine inside action methods while debugging, but during unit testing the action method when I inject automapper service I find that service.map is returning null. But while debugging the mapping is fine. I'm not being able to find the reason, trying for over 4 hrs. I have a domain class called Interview & its corrosponding viewmodel as InterviewModel. I have initialized mapping as CreateMap(); in automapper profile config, that has been called from global startup method. Below is the controller & action...
public class NewsAndViewsController : Controller
{
private IInterviewRepository repository;
private IMappingService mappingService;
public NewsAndViewsController(IInterviewRepository productRepository, IMappingService autoMapperMappingService)
{
repository = productRepository;
mappingService = autoMapperMappingService;
}
[HttpPost, ValidateAntiForgeryToken]
[UserId]
public ActionResult Edit(InterviewModel interView, string userId)
{
if (ModelState.IsValid)
{
var interView1 = mappingService.Map<InterviewModel, Interview>(interView);
**// THE ABOVE LINE RETURNING NULL WHILE RUNNING THE BELOW TEST, BUT NOT DURING DEBUGGING**
repository.SaveInterview(interView1);
TempData["message"] = string.Format("{0} has been saved", interView.Interviewee);
return RedirectToAction("Create");
}
return View(interView);
}
}
[TestMethod]
public void AddInterview()
{
// Arrange
var interviewRepository = new Mock<IInterviewRepository>();
var mappingService = new Mock<IMappingService>();
var im = new InterviewModel { Interviewee="sanjay", Interviewer="sanjay", Content="abc" };
mappingService.Setup(m => m.Map<Interview, InterviewModel>(It.IsAny<Interview>())).Returns(im);
var controller = new NewsAndViewsController(interviewRepository.Object, mappingService.Object);
// Act
var result = controller.Edit(im, "2") as ViewResult;
// Assert - check the method result type
Assert.IsNotInstanceOfType(result, typeof(ViewResult));
}
In your test you've got your Interview and InterviewModel classes crossed up in the mappingService.Setup() call (as an aside, I think you could use better naming conventions, or don't use var, to keep your objects clear - "im", "interview" and "interview1" don't make it easy to follow which is the model and which is the view object).
Try this:
[TestMethod]
public void AddInterview()
{
// Arrange
var interviewRepository = new Mock<IInterviewRepository>();
var mappingService = new Mock<IMappingService>();
var interview = new Interview();
var im = new InterviewModel { Interviewee="sanjay", Interviewer="sanjay", Content="abc" };
mappingService.Setup(m => m.Map<InterviewModel, Interview>(im).Returns(interview);
var controller = new NewsAndViewsController(interviewRepository.Object, mappingService.Object);
// Act
var result = controller.Edit(im, "2") as ViewResult;
// Assert - check the method result type
Assert.IsNotInstanceOfType(result, typeof(ViewResult));
}
Is there as easy way to convert string URL to RouteValueDictionary collection? Some method like UrlToRouteValueDictionary(string url).
I need such method because I want to 'parse' URL according to my routes settings, modify some route values and using urlHelper.RouteUrl() generate string URL according to modified RouteValueDictionary collection.
Thanks.
Here is a solution that doesn't require mocking:
var request = new HttpRequest(null, "http://localhost:3333/Home/About", "testvalue=1");
var response = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var values = routeData.Values;
// The following should be true for initial version of mvc app.
values["controller"] == "Home"
values["action"] == "Index"
Hope this helps.
You would need to create a mocked HttpContext as routes constrains requires it.
Here is an example that I use to unit test my routes (it was copied from Pro ASP.Net MVC framework):
RouteCollection routeConfig = new RouteCollection();
MvcApplication.RegisterRoutes(routeConfig);
var mockHttpContext = new MockedHttpContext(url);
RouteData routeData = routeConfig.GetRouteData(mockHttpContext.Object);
// routeData.Values is an instance of RouteValueDictionary
//...
I wouldn't rely on RouteTable.Routes.GetRouteData from previous examples because in that case you risk missing some values (for example if your query string parameters don't fully fit any of registered mapping routes). Also, my version doesn't require mocking a bunch of request/response/context objects.
public static RouteValueDictionary UrlToRouteValueDictionary(string url)
{
int queryPos = url.IndexOf('?');
if (queryPos != -1)
{
string queryString = url.Substring(queryPos + 1);
var valuesCollection = HttpUtility.ParseQueryString(queryString);
return new RouteValueDictionary(valuesCollection.AllKeys.ToDictionary(k => k, k => (object)valuesCollection[k]));
}
return new RouteValueDictionary();
}
Here is a solution that doesn't require instantiating a bunch of new classes.
var httpContext = context.Get<System.Web.HttpContextWrapper>("System.Web.HttpContextBase");
var routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(httpContext);
var values = routeData.Values;
var controller = values["controller"];
var action = values["action"];
The owin context contains an environment that includes the HttpContext.