Directly call ASP.NET controller action from a different controller - c#

I have a situation where I need to redirect to an ASP.NET MVC action in a different controller. I can't use RedirectToAction because I must POST the action's parameters to keep them out of the URL.
I attempted to instantiate and call the other controller's action directly like this:
OtherController myOtherController = new OtherController();
myOtherController.ControllerContext = new ControllerContext(this.ControllerContext.RequestContext, myOtherController);
return await myOtherController.Edit(myGuid);
When I do this, the other controller's code executes, but I end up with this error:
The model item passed into the dictionary is of type
'System.Data.Entity.DynamicProxies.OtherModel_BBCEF7C9378F4C4F097CC08FA2E508B8BD8D865E33093E31959919087A31348E',
but this dictionary requires a model item of type 'ThisModel'.
Does anyone know if I can get this working using the current approach? Assuming I must use an HTTP POST action and cannot have parameters in the URL, is there a different approach that you would recommend to achieve this result (other than combining the controllers)?
Edit:
Note that I don't think I can post directly from the client because I would need to nest Html.BeginForm.

You should really just be using RedirectToAction to push the browser to the action you want instead of trying to do it like this. As you can see from #Andrei Olariu code a lot of things happen under the hood during the construction of your controller (DI then context and parameter mappings) that really shouldn't be done manually and can easily be screwed up leading to hours wasted wondering why certain things are not behaving as expected.

I hope I understood what you're trying to do. This is what I'm successfully using to execute an action on a different controller.
private void ExecuteErrorAction(string action, HttpContextWrapper httpContext, Exception exception)
{
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = action;
routeData.Values["exception"] = exception;
IController errorController = DependencyResolver.Current.GetService<ErrorController>();
var context = new RequestContext(httpContext, routeData);
errorController.Execute(context);
}
I think that in your case this could look like:
private void ExecuteOtherAction(string myGuid, HttpContextWrapper httpContext)
{
var routeData = new RouteData();
routeData.Values["controller"] = "OtherController";
routeData.Values["action"] = "Edit";
routeData.Values["myGuid"] = myGuid;
IController otherCntroller = DependencyResolver.Current.GetService<OtherController>();
var context = new RequestContext(httpContext, routeData);
otherCntroller.Execute(context);
}
This is assuming that your Edit action on the OtherController takes a string called myGuid as a parameter.
Let me know if this helps.

Related

mvc: Model.IsValid not working if Iam using Request.Form

Iam a beginner in MVC.
If iam using below code then Model.IsValid is not validating the object which in this case is Customer.
public ActionResult Submit()
{
Customer custObj = new Customer();
custObj.CustomerCode = Request.Form["CustomerCode"];
custObj.CustomerName = Request.Form["CustomerName"];
if (ModelState.IsValid)
return View("Load", obj);
else
return View("EnterCustomer");
}
While if Iam passing the Customer object in parameter then Model.IsValid is working perfectly.
public ActionResult Submit(Customer obj)
{
//Customer custObj = new Customer();
//custObj.CustomerCode = Request.Form["CustomerCode"];
//custObj.CustomerName = Request.Form["CustomerName"];
if (ModelState.IsValid)
return View("Load", obj);
else
return View("EnterCustomer");
}
Can any1 help me in getting to know the reason.
It doesn't work beause MVC never bound to the model itself. You manually overrode it so MVC has no clue whether the model is valid or not. It doesn't event know that custObj is the model.
ModelState.IsValid is set before your action method is called, so in your second example, when you allow MVC to bind to the model itself, it works. In the first, it doesn't work because you create the model and do manual binding to it.
Update
You can, however, also manually run the model validation by calling ValidateModel or TryValidateModel on the controller.
Documentation:
ValidateModel: https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.validatemodel(v=vs.100).aspx
TryValidateModel: https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.tryvalidatemodel(v=vs.100).aspx
As mentioned in other answers, your model is already validated before the action 'Submit' is called. So, when you are changing the model from inside your action, you will have to manually validated the model. You may use below code for it.
var context = new ValidationContext(custObj, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(custObj, context, validationResults, true);
if (isValid)
return View("Load", obj);
else
return View("EnterCustomer");
use below url for further details.
http://odetocode.com/blogs/scott/archive/2011/06/29/manual-validation-with-data-annotations.aspx

Get Controller-instances from Routes.MapHttpRoute

In an existing C# Web project here at my Job I've added a Web API part.
In four of my own classes that I use for the Web API I need to access some of the existing Controller-classes. Right now I just create a new Instance of them and everything works as intented: ProductController controller = new ProductController();
Still, creating a new ProductController while one should already exist obviously isn't a good practice. I know the Controllers are created in the Config-file in the Routes.MapHttpRoute, since it's using the C# Web MVC method. Below I've copied that piece of code:
config.Routes.MapHttpRoute(
name: "Default",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "MyProject.Controllers" }
);
route.DataTokens["UseNamespaceFallback"] = false;
I've tried to access these Controllers in my one of my API-classes like so:
private void getControllerInstance()
{
var url = "~/Products";
// Original path is stored and will be rewritten in the end
var httpContext = new HttpContextWrapper(HttpContext.Current);
string originalPath = httpContext.Request.Path;
try
{
// Fake a request to the supplied URL into the routing system
httpContext.RewritePath(url);
RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);
// If the route data was not found (e.g url leads to another site) then authorization is denied.
// If you want to have a navigation to a different site, don't use AuthorizationMenu
if (urlRouteData != null)
{
string controllerName = urlRouteData.Values["controller"].ToString();
// Get an instance of the controller that would handle this route
var requestContext = new RequestContext(httpContext, urlRouteData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
// TODO: Fix error (The controller for path '/Products' was not found or does not implement IController.) on this line:
var controllerbase = (ControllerBase)controllerFactory.CreateController(requestContext, controllerName);
controller = (ProductController)controllerbase;
}
}
finally
{
// Reset our request path.
httpContext.RewritePath(originalPath);
}
}
As you might have noticed by the TODO-comment, at the line var controllerbase = (ControllerBase)controllerFactory.CreateController(requestContext, controllerName);, I get the following error:
HttpException was unhandler by user code: The controller for path '/Products' was not found or does not implement IController.
Does anyone know how to fix this error? Has this got something to do with one of the following two lines of the code in the Config-file?
namespaces: new[] { "MyProject.Controllers" }
route.DataTokens["UseNamespaceFallback"] = false;
Or did I do something else wrong?
A tip to everyone: Don't continue programming when you are very, very tired.. Anyway, everything was correct except for a small flaw:
My API Controller is called ProductsController and my normal (default) controller is called ProductController. In the method above I use:
var url = "~/Products";
To access the ProductController..
So, after removing the "s" (and for good measure make everything lower case) I have the following instead:
var url = "~/product";
And now it works..

Is it possible to retrieve a Controller instance for some Url string in ASP.NET MVC3?

if i have an route like /foo/bar/pewpew .. is it possible to get an instance of the controller which that route maps too?
To get the controller name, you can call create a fake HttpContextBase that returns your URL in its Request, then pass it to RouteTable.Routes.GetRouteData and check the area and controller values.
To get the controller instance, pass a RequestContext consisting of that HttpContextBase and RouteData to ControllerBuilder.Current.GetControllerFactory.CreateController.
Try,
var wrapper=new HttpContextWrapper(System.Web.HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(wrapper);
var controller = ControllerBuilder.Current.GetControllerFactory().CreateController(new RequestContext(wrapper, routeData), routeData.Values["controller"].ToString());
Update, you can use this instead.
var wrapper = new HttpContextWrapper(new System.Web.HttpContext(new HttpRequest(null, "http://localhost:4836/", null), new HttpResponse(new StringWriter())));

Accessing System.Web.Routing.RequestContext from static context in MVC 2.0

I need to use System.Web.Routing.RequestContext in a view model in order to call HtmlHelper.GenerateLink().
In MVC 1.0 it was possible to get the context statically by casting the current IHttpHandler:
var context = ((MvcHandler) HttpContext.Current.CurrentHandler).RequestContext;
Now the project has been upgraded to MVC 2.0 and this exception is thrown on the cast:
Unable to cast object of type 'ServerExecuteHttpHandlerWrapper' to type 'System.Web.Mvc.MvcHandler'.
I'm not sure if it's relevant but this is being run in .NET 4.0 on IIS6.
I need to use System.Web.Routing.RequestContext in a view model in order to call HtmlHelper.GenerateLink().
While in theory you could write:
var rc = HttpContext.Current.Request.RequestContext;
in practice you should absolutely never be doing something like this in a view model. That's what HTML helpers are supposed to do:
public static MvcHtmlString GenerateMyLink<MyViewModel>(this HtmlHelper<MyViewModel> html)
{
MyViewModel model = html.ViewData.Model;
RequestContext rc = html.ViewContext.RequestContext;
//TODO: use your view model and the HttpContext to generate whatever link is needed
...
}
and in your strongly typed to MyViewModel view simply:
<%= Html.GenerateMyLink() %>
I don't know what you wanna do with the System.Web.Routing.RequestContext? check out:
var context = new HttpContextWrapper(System.Web.HttpContext.Current);
var routeData = RouteTable.Routes.GetRouteData(context);
// Use RouteData directly:
var controller = routeData.Values["controller"];
// Or with via your RequestContext:
var requestContext = new RequestContext(context, routeData);
controller = requestContext.RouteData.Values["controller"]

How can I get controller type and action info from a url or from route data?

How can I get the controller action (method) and controller type that will be called, given the System.Web.Routing.RouteData?
My scenario is this - I want to be able to do perform certain actions (or not) in the OnActionExecuting method for an action.
However, I will often want to know not the current action, but the "root" action being called; by this I mean I may have a view called "Login", which is my login page. This view may include
another partial view "LeftNav". When OnActionExecuting is called for LeftNav, I want to be able to determine that it is really being called for the "root" aciton of Login.
I realise that by calling RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext), I can get the route for the "root" request, but how to turn this into
method and type info?
The only solution I have so far, is something like:
var routeData = RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext)
var routeController = (string)routeData.Values["controller"];
var routeAction = (string)routeData.Values["action"];
The problem with this is that "routeController" is the controller name with the "Controller" suffix removed, and is not fully qualified; ie it is "Login", rather than "MyCode.Website.LoginController".
I would far rather get an actual Type and MethodInfo if possible, or at least a fully qualified type name.
Any thoughts, or alternative approaches?
[EDIT - this is ASP.Net MVC 1.0]
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var type1 = filterContext.Controller.GetType();
var type2 = filterContext.ActionDescriptor
.ControllerDescriptor.ControllerType;
}
OK, sorry, I missed the "root" part.
Then, another way, you can save controller type to thread storage. Pseudocode:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!Thread.LocalStorage.Contains("root_controller"))
Thread.LocalStorage["root_controller"] =
filterContext.ActionDescriptor
.ControllerDescriptor.ControllerType;
}
Just an idea. I'm sure thread local storage is available in C#. The key idea here is that you save it only for first request, thus it's always root controller.
Here is the solution I compiled from various sources. The url variable should contain the URL of the action:
url = "YOUR URL";
// Original path is stored and will be rewritten in the end
var httpContext = new HttpContextWrapper(HttpContext.Current);
string originalPath = httpContext.Request.Path;
try
{
// Fake a request to the supplied URL into the routing system
httpContext.RewritePath(url);
RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);
// If the route data was not found (e.g url leads to another site) then authorization is denied.
// If you want to have a navigation to a different site, don't use AuthorizationMenu
if(urlRouteData != null)
{
string controllerName = urlRouteData.Values["controller"].ToString();
string actionName = urlRouteData.Values["action"].ToString();
// Get an instance of the controller that would handle this route
var requestContext = new RequestContext(httpContext, urlRouteData);
var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
var controller = (ControllerBase) controllerFactory.CreateController(requestContext, controllerName);
// Find the action descriptor
var controllerContext = new ControllerContext(httpContext, new RouteData(), controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
}
}
finally
{
// Reset our request path.
httpContext.RewritePath(originalPath);
}
public Type ControllerType(string controllerName)
{
var fullName = controllerName + "Controller";
var assemblyName = Assembly.GetExecutingAssembly().FullName;
return Activator.CreateInstance(assemblyName, fullTypeName).GetType();
}
public MethodInfo ActionMethodInfo(string actionName, Type controllerType)
{
return controllerType.GetMethod(actionName);
}
Are you thinking of an implementation similar to this? Some Try/Catches required!
MvcSiteMapProvider does this. Here is the code for this particular thing.
Here is the code

Categories

Resources