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.
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.
We have a model (say, List<string>). The function that builds the list is non-deterministic and the output is needed to be referenced in both controller and the view during the lifetime of the request. Since it's per-request, it cannot be static or singleton.
It's a common structure and it can be referenced from any view or controller.
Since we can't access controller from the view (by principle, and we agree), we cannot keep it in the controller. We're currently keeping it in the ViewData dictionary and initialize it in the controller, or the view (if the controller didn't need it).
We think that using ViewData for this purpose may not be ideal since it's not created to be consumed by a controller in the first place. Is there a better way to share common per-request data between Controller and the View? If not we'll stick with ViewData.
There is HttpContext.Items dictionary but I'm not sure if it fits to this purpose.
the output is needed to be referenced in both controller and the view during the lifetime of the request
The way MVC works, the Action code in the Controller is executed, and the resulting data is passed to the view engine that draws the page using the info you passed either with the call to View(data) or in the ViewData dictionary.
I don't know what you are trying to do, but it sounds like it's more a problem of a bad approach than a technical one (I might be wrong, though).
Could you explain why you need the controller while the View is rendered? If you need any logic associated with the List (to process it or do anything with it) I would just create a new class that extends List<T>, add the logic to that class instead of the controller, and pass an object of that class to the View, either using View() or ViewData[].
What is exact thing you're trying to do?
Seems like you just asking about the way to pass some data from the Controller to the View which is rather trivial task. Just use ViewData, yes, or ViewBag in MVC3 case or use ViewModels.
Or there is somewhat special case? What does "referencing from Controller and from View" mean? Where the data is coming from? Usually the case is that Controller prepares data for the View and passes it as an ActionResult (or better, as a ViewModel). View should never take some data on its own bypassing the controller.
Controller action should always be called be first. If you have multiple controllers calling the same view/partial view then you should be refactoring the code to one method and call that.
ViewData is the solution to do this, if your really wanting "once access" type information then maybe TempData but ViewData is designed for this.
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"] })%>
Recently I started working with MVC, before that I used "classic" ASP.NET.
After using Ruby on Rails (RoR), I wonder how to implement POST request handling in MVC similar to how RoR operates. In RoR you use the Post method, so you need only one function for a view.
In ASP.NET MVC I need to use 2 separate functions for GET and for POST, so I need to initialize the same data twice, and I don't like to repeat something in my code.
How can I check if the request is POST in one method?
Update:
Solution is found: I have to use Request.HttpMethod.
Thank you!
I came across this question wanting to know the same thing. Below is a detailed description of my situation and the solution that I used (which utilizes the other answers provided here). I originally tried to use the two separate method approach, but I ran into a problem when the method signatures of these methods became identical.
I have a page that displays report data. At the top of the page there is a form with some fields, which allow the user to specify report parameters such as start date, end date, etc.
I originally approached this by creating two separate methods to handle the Get and the Post methods. The post method would redirect the browser to the get method so that any parameters that were specified would be added to the query string and so that the browser would not prompt the user with a dialog saying that it is going to resend the data that they entered if they refresh. Note: I realized later that I could accomplish this by setting the method attribute of my form element to "Get", but I think ideally a controller shouldn't have knowledge of how a view is implemented, so in my opinion that is irrelevant.
As I developed these two methods I eventually found myself in a situation where the method signatures became identical. Furthermore, my code for these two methods became nearly identical, so I decided to merge them into a single method and to just check the request verb so that I could do something slightly different when the request is not a "Get". A distilled example of my two methods is shown below:
// this will not compile because the method signatures are the same
public ActionResult MyReport(DateRangeReportItem report)
{
// if there are no validation errors and the required report parameters are completed
if (ModelState.IsValid && report.ParametersAreComplete)
{
// retrieve report data and populate it on the report model
report.Result = GetReportData(report.CreateReportParameters());
}
return View(report);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyReport(DateRangeReportItem report)
{
if (ModelState.IsValid && report.ParametersAreComplete)
{
// redirect to the same action so that if the user refreshes the browser it will submit a get request instead of a post request
// this avoids the browser prompting the user with a dialog saying that their data will be resubmitted
return RedirectToAction("MyReport", new { StartDate = report.StartDate, EndDate = report.EndDate });
}
else
{
// there were validation errors, or the report parameters are not yet complete
return View(report);
}
}
Why am I accepting a model object as the parameter to my get method? The reason is that I wanted to take advantage of the validation logic already built into the model object. If someone navigates to my page directly with all parameters already specified in the query string, then I want to go ahead and retrieve the report data and display it on the page. However, if the parameters specified in the query string are invalid then I also want validation errors to appear on the page. By putting my model object as the parameter, the MVC framework will automatically attempt to populate it and will capture any validation errors without any additional work on my part.
I used the other answers posted for this question to create a RequestHttpVerb property on a base controller class in my project:
public HttpVerbs RequestHttpVerb
{
get { return (HttpVerbs)Enum.Parse(typeof(HttpVerbs), this.Request.HttpMethod, true); }
}
So finally my consolidated method looks like the following:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult MyReport(DateRangeReportItem report)
{
// check if there are any validation errors in the model
// and whether all required report parameters have been completed
if (ModelState.IsValid && report.ParametersAreComplete)
{
// this is unnecessary if the form method is set to "Get"
// but within the controller I do not know for sure if that will be the case in the view
if (HttpVerbs.Get != this.RequestHttpVerb)
{
// redirect to the same action so that if the user refreshes the browser it will submit a get request instead of a post request
// this avoids the browser prompting the user with a dialog saying that their data will be resubmitted
return RedirectToAction("MyReport", new { StartDate = report.StartDate, EndDate = report.EndDate });
}
// there were no validation errors and all required report parameters are complete
// retrieve report data and populate that data on the model
report.Result = GetReportData(report.CreateReportParameters());
}
// display the view with the report object
// Any model state errors that occurred while populating the model will result in validation errors being displayed
return View(report);
}
That's my current solution to the problem. I would prefer not to have to check the Request.HttpMethod property in order to determine whether I needed to perform the redirect, but I didn't see another solution to my problem. I would have been fine with keeping two separate methods to handle Get and Post requests, but the identical method signature prevented this. I would have preferred to rename my Post action handler method to avoid the method signature conflict and to use some mechanism to indicate to the MVC framework that my renamed method should still handle the "MyReport" action, but I am not aware of any such mechanism in the MVC framework.
You only need separate methods for GET and POST if their method signatures differ, there's no reason why one action method can't handle GET and POST methods.
If you need to know whether it was a GET or POST, you could check using Request.HttpMethod in your action, but I would advise using a separate method decorated with the [AcceptVerbs(HttpVerbs.Post)] attribute as suggested by the other posters.
You don't check in ASP.NET MVC. You decorate your method with the [AcceptVerbs(HttpVerbs.Post)] attribute to indicate that the method applies to post only, and accept the model in the method used to handle the post.
I'd strongly suggest doing the walkthrough for NerdDinner to understand more about the ASP.NET MVC framework.
You may take a look at the Request.HttpMethod property.
Correct way to do is using ModelBinding during Post request.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(EmployeeViewModel model)
{
//validate data, save employee, handle validation errors...
}
This way you will not have to init your data again.
I have a service which has a method that's called when a certain controller method is triggered.
My service returns a custom result object PlacementResult in which I want to communicate errors that may have happened (validation) back to the controller method.
Should PlacementResult have a ModelState or a ModelStateDictionary to communicate errors back to the controller (and finally view)? How would I string this together?
Finally, how do I get the ModelState/ModelStateDictionary (whichever you tell me I should choose) back into the view (highlighting the appropriate text box, show the error message etc.)?
Thank you !
This is a good link that shows how a service can perform validation and communicate the result back to the controller:
http://www.asp.net/mvc/tutorials/validating-with-a-service-layer-cs (fixed link)
No, you do not want to add a ModelStateDictionary to your result type. There is already a ModelStateDictionary on the Controller (in the ModelState property). It is not appropriate for results to set the controller's model state. That should be done during binding or within the controller action itself. Use a custom model binder if you need to.
Your choose one can see the model state errors by examining the controller's ViewData.ModelState property.
Your PlacementResult should return a dictionary object or a List which you should merge with the model state at the beginning of each action.
If you step through you will notice the controllers model state dictionary contains all your input fields, their values and the errors associated with them. You want to merge the PlacementResult errors into the model state dictionary at the appropriate keys. This is how the view engine knows which fields to flag as invalid.
ModelState.Merge(PlacementResult);
if(ModelState.IsValid)
{
...
}
I don't know what your PlacementResult looks like, see if you can possibly utilize this in your view:
ModelState.AddModelError(ErroredProperty, ErrorMessage);
Make sure you return the object that failed back to the view
return View(myObjectInstance);
Based on SoC I think you have to return errors from your services and merge them in your ModelState if needed.
But our objective is maintain decoupling and also to use ModelState.Merge() method. it isn't?
There is an concrete implementation that could help
You can pass the Controller to your method, since the Controller class contains the ModelState property. Once in your method you can do the following:
private PlacementResult BuildResult(Controller controller)
{
controller.ModelState.AddModelError(propertyName, errorMessage);
}
In your action...
BuildResult(this);
if(ModelState.IsValid) {...