I have two different objects: contracts, and task orders. My requirements specify that in order to view the Details for either object, the Url should be "http://.../Contract/Details" or "http://.../TaskOrder/Details" depending on which type. They are both very similar and the details pages are almost identical, so I made a class that can either be a contract or a task order, and has a variable "objectTypeID" that says which type it is. I wrote the action "Details" in the task order controller, but now I want to call that from the contract controller instead of recopying the code.
So is there any way to have the url still say ".../Contract/Details" but call the action in the TaskOrder controller instead? I tried using
TaskOrderController TOController = new TaskOrderController();
TOController.Details(id);
This would have worked except that I can't use the HttpContext.Session anymore, which I used several times in the action.
Why are you calling a controller from a controller? A controller action should be called via a route and return a view.
If you have common code used by two separate controllers then you should be looking to abstract this code to another class.
RedirectToAction("Details","To");
In addition, add routing parameters if you need to.
Also, maybe you need a BaseController class which these two controllers inherit from and which implement the same Details action, but based on the objectTypeID do slightly different things.
Create a base class for the controller. Like DetailsController
Put your details code in there, and have it accept an typeId.
Then have your two controllers derive from that base class, and have their Details action call the base class passing in the id
Thanks David, I should be calling it from the view.
All I needed was the following line in my Contract/Details.aspx page:
<%= Html.Action("Details", "TaskOrder", new { id = ViewData["id"] })%>
Related
I'm developing a large ASP.NET Core 2 web application but I still get confused with URLs.
When fist learning, I thought URL's took on the name of the View, but discovered they come from the Controller method.
Here is an example to convey my issue:
I have a controller method named Daysheet, which returns a view and model with the same name.
In the Daysheet view, I call various controller methods from Javascript to perform specific actions. One of them is called AssignStaff which takes two integer parameters.
In the AssignStaff method I again return the Daysheet view with model, but now my URL is "AssignStaff"!
I can't just do a redirect because the whole Daysheet model is not being passed to the AssignStaff method.
I have many situations like this where after calling an action, I end up with another URL that I don't want.
UPDATE/EDIT
Thanks for assistance and apologies if my explanation is confusing. I simply have a view called Daysheet that uses a model. I want to call various controller methods to perform various actions, but I want to stay on the "Daysheet" view/URL.
As mentioned, I can't just redirect because in the action method I no longer have the whole model from the Daysheet view. Also, if I redirect I can't pass the whole model because that causes an error saying the header is too long. I think my only choice may be to use ajax for the actions so that the URL doesn't change.
When you just do Return View("") name in a Controller Action, the URL will be the name of the Action you are using.
If you want to redirect to some specific Action, that will help to make sure the Url matches to where you are. You might want to read more about it here.
To do so, use:
RedirectToAction()
The URLs your application responds to are called "routes", and they are either created by convention or explicitly. The default is by convention, of course, which is a URL in the form of /{controller=Home}/{action=Index}. Index is the default action if that portion of the route is left off, so a request to /foo will by convention map to FooController.Index. HomeController is the default controller, so an empty path (e.g. http://sample.com) will by convention invoke HomeController.Index.
Razor Pages have their own conventions. These do somewhat follow the file system, but exclude the Pages part of the path. So a Razor Page like Pages/Foo/MyRazorPage.cshtml, will load up under /Foo/MyRazorPage.
There there is the Route attribute, which allows you to specify a totally custom route. This attribute can be applied to a controller class and individual actions in the class. For example:
[Route("foo")]
public class MyAwesomeController
{
[Route("bar")]
public IActionResult MyAwesomeAction()
{
return View();
}
}
With that, a request to /foo/bar will actually invoke MyAwesomeController.MyAwesomeAction.
If so, how can I call it from my View?
The controller action that I have in mind is something like this:
public ActionResult UrunYonetimi<T>(int param = 0)
{
// doing stuff
}
Can we have an action like this? If so, how can I specify that T from my e.g #Url.Action() call?
I think I've come to an understanding for this issue:
I guess we need to declare our nice generic functions in the model rather than in the controller, and handle different types from there. We can and probably should have different actions and thus different URLs for every model type, but they will all call the same function from the model, only with different types.
I have a controller with several action methods requiring the same list of data from a certain database. Since most of the actions were going to need access to the list, I quickly populated a private member variable with necessary list of data items directly in the constructor of my controller.
All was well and good until the database went down and an exception was thrown in the constructor. Apparently, that circumvents the normal HandleError functionality.
My goal is for this exception to be caught and the user redirected to an error view.
What is the proper way to load the data for all actions?
Is it appropriate to put a database call in OnActionExecuting?
Is there some way to do decorate the specific actions with an attribute that loads the data?
Am I over-thinking it? (After all, I could just drop a private method in the controller and call it from each action requiring the data)
You could create the private method and have it populate your list (if it's not already populated) and then return the list. This way your only calling the method to populate it when it's needed the first time, and you take fragile code out of your controller's constructor. It's going to much easier to handle the exception in your action methods than elsewhere.
Controllers (as objects) are being instantiated for every request. Therefore, there is no need to optimize data within controller which would be "reused" in many actions (as Jeff Reddy suggested). Unless you call an action method explicitly from another action method (which is bad practice anyway).
Make a private method GetData() that gets data from database and call it in every action.
However, you probably do want to avoid expensive database round-trips that get the same data over and over, then consider using HttpRuntime.Cache. You could save data there on the first call to GetData() and retrieve it from cache on subsequent requests.
If you need the model inside all your controller actions you could define a custom model binder for a given model and overriding the BidModel method which will query the database and populate this model. Then your controller actions could take this model as action argument:
public ActionResult Foo(MyModel model)
{
...
}
public ActionResult Bar(MyModel model)
{
...
}
If you don't need the model inside each action but inside each view you could externalize it as a widget using the Html.RenderAction helper.
In a controller, is it possible to return the view of an action from ANOTHER controller? The other option is to return a partial view, which uses Html.Action(...) to return the view from the other controller, but I was wondering if there's anything cleaner. Thanks.
If it's just the view you want to reuse, you can pass in the path to the view. For example:
public ActionResult MyAction()
{
// do your model magic here
return View( "~/Views/OtherController/View.aspx", model );
}
Or you can move the view to Views/Shared like Kyle already suggested.
Yes, if that view is a Shared view. Place the view in the Views/Shared folder in your MVC Project, then both controllers will be able to return it.
If you want to invoke an action on another controller, you can use Controller.RedirectToAction() and pass in the action and controller name.
However, this adds an additional server round trip. If you want to avoid that, you can use the TransferResult class shown here:
How to simulate Server.Transfer in ASP.NET MVC?
I ended up using my original solution, which was having a shared view that invokes an action. It was much less code than I needed. Thanks.
Here's a strategy I use to invoke another action without having to create a special view just for that purpose:
Create a shared view which takes a model that defines an Action, Controller, and RouteValues, and whose sole responsibility is to call RenderAction with the values on that model.
Next create a helper method on your base controller class that takes an Action, Controller, and RouteValues as parameters, and returns the ViewResult for this shared view. That way, you can reuse this helper method and shared view on all your controllers any time you want to render some other action from another action's context.
Of course, if it's just the view and not the action that you want to invoke, Marnix's answer is correct.
All my controllers are based off of a BaseController, to share properties between them and override OnActionExecuting to set some values based on the route.
I'm creating a BaseViewData class to do the same for all my view data.
At the moment I'm populating the view data like so (C#):
var viewData = new BaseViewData
{
Name = "someName",
Language = "aLanguage",
Category = "aCategoryName"
};
I do this in every action that requires the view data. Some of the properties are common, need to be set throughout every action. Is there a way to set some of the properties on a more global scale?
If I instantiate the BaseViewData class in the OnActionExecuting method on the BaseController, how do I access the BaseViewData properties from the action in the regular controllers (derived from the BaseController)?
Update in response to Dennis Palmer:
I'm essentially doing this because of a nagging issue I'm having with ViewData["lang"] not being populated randomly on some requests. ViewData["lang"] contains "en" if the language is English, and "ja" if it is Japanese (well, it's supposed to anyway). I populate ViewData["lang"] inside OnActionExecuting on the BaseController.
In my view, I make a call to some partial views based on the language:
<% Html.RenderPartial(ViewData["lang"] + "/SiteMenu"); %>
But I'm randomly getting errors thrown that state "Cannot find /SiteMenu", which points to the fact that ViewData["lang"] has no value. I just cannot find any reason why ViewData["lang"] would not get populated. So, I'm rewriting the site to use ONLY strongly typed view data (and setting some hard defaults). But if another method is better, I'll go that way.
Thank you!
I'm not sure I follow exactly what you're trying to do, but if your view is using values in the route to display certain information, it seems like adding your own extension methods for HtmlHelper would be a better way to go.
Are Name, Language and Category contained in your routes? If so, then HtmlHelper will have access to the route info and can determine what to display via the extension methods. What is the correlation between your routes and what your views need to know?
Update: Is lang part of your route? If so, then I would still contend that you could write an HtmlHelper extension method that looks at the route data directly and determines which partial view to render. That way your controller wouldn't even need to worry about setting the ViewData["lang"]. The view would always know how to render based on the route.
Update 2: I think dismissing use of an HtmlHelper extension method because it re-evaluates the route data might be a case of premature optimization. Your controller inheritance scheme sounds overly complex and you asked the question because the way you were setting ViewData was unreliable. I doubt that pulling the value from route data would be much, if any, less efficient than setting and reading from ViewData.
From your comment:
In the controller I use the lang value
to determine which view to show as
well.
That only makes me think that there are more pieces of your system that I'd need to see in order to give better advice. If you have separate views for each language then why does the view need to be told which language to use?
Another alternative to consider would be using nested master pages. You could have a single master page for your site layout and then a nested master page for each language that just contains a hard coded lang value.
Perhaps instead of this inheritance scheme you have, you can just use action filters to add the data you need.