I am working on a MVC4 application with Razor.
users will be able to edit entities on this page, but since MVC-style urls look like this:
~/{Entity}/Edit/{Id}
~/MyEntity/Edit/1
~/MyEntity/Edit/2
I fear that the client will not load the form from the cache.
i am currently always responding the empty form and filling data later with an ajax request. I'd love to keep the url style and somehow tell the client that he already got the form (from a request with different Id)
You can add another parameter to your controller actions
for example right now you have
~/MyEntity/Edit/1
so i am assuming it looks something like
public ActionResult Edit(int id)
you can simply add another parameter:
public ActionResult Edit(int id, int loadFromCache)
{
if(loadFromCache == 1)
{
//do something
}
else
{
//proceed regularly
}
}
this way you can pass another parameter by which you will be able to do an if statement
~/MyEntity/Edit/1?loadFromCache=1
Related
I'm new to MVC, so I'm trying to figure out some best practices.
Suppose I have a controller HomeController method Index(MyViewModel model):
public ActionResult Index(MyViewModel model)
{
//if loading the page for the first time, do nothing
//if the page has been posted data from somewhere, then I want to use
// some of the arguments in model to load other data, like say search results
}
When I navigate to the /Index page, I (myself) expect the model object to come through as null, but it doesn't. MVC (somehow) creates a MyViewModel for me.
My question is, what's the best way or most consistent to determine if model was created automatically, or via a post?
Ideas:
Create a property on MyViewModel that gets set when the view is posting back
Check for if the Request.HttpMethod == "GET" or "POST"
Something else?
You should use different actions for your GET and POST requests. Don't try and make a single method do too much.
[HttpGet]
public ActionResult Index()
{
// handle the GET request
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (ModelState.IsValid)
{
// it's a post and the data is valid
}
}
The correct method will then be called depending on whether it's a GET or POST
Create two actions, one which accepts a model instance and one which doesn't.
Even though you're "going to the same page" you are in fact performing two distinctly different actions. The first action loads an initial page, the second action posts some value to be acted upon. Two actions means two methods:
[HttpGet]
public ActionResult Index()
{
// perform any logic, but you probably just want to return the view
return View();
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// respond to the model in some way
return View(model);
// or return something else? a redirect? it's up to you
}
Note that this kind of breaks your restful URLs. Consider semantically what you're doing in these actions:
Viewing an index
Posting to an index
The first one makes sense, but the second one probably doesn't. Normally when you POST something you're doing something related to a model or action of some sort. "Index" doesn't really describe an action. Are you "Create"-ing something? Are you "Edit"-ing something? Those sound like more meaningful action names for the POST action.
I have achieved creating the WebApi itself and I can browse it from the browser and get the output.
The thing which is not working for me is that I am trying to consume the WebAPI from an MVC Controller, and I have written the code for calling the WebAPI in my "cshtml" view.
But it doesn't work, as I am getting the error in loading the page, I understand I am doing something wrong. So the first question would be: am I doing this correctly, or is it completely wrong to create a WebAPI part in an MVC Project and then try to consume it in the same MVC project from the controller?
To answer your question, it's actually "as designed" and recommended to have your WebAPI and MVC client inside the same project. This is why you have both a RouteConfig.cs and a WebApiConfig.cs inside your MVC project. RouteConfig.cs is for your MVC controllers, and WebApiConfig.cs is obviously for your Api Controllers.
To have both in the same project is easy. What I do is add a folder named "API" in my root, and place all my WebAPI controllers in there. Keep in mind, and I'm sure you know, that the only difference between a WebAPI controller and an MVC controller is that a WebAPI controller inherits ApiController which is part of System.Web.Http (I believe) whereas an MVC controller inherits Controller which is part of System.Web.MVC.
Below is the proper way to make GET/PUT/DELETE/POST requests TO your WebAPI FROM an MVC front end. It doesn't matter if it's in the same project or not because you specify the WebAPI URL in your controller's constructor. If your WebAPI is on a different server than your front end MVC app, you will need to enable CORS support, which is a feature available in WebAPI version 2 and above.
This is the proper way to call a WebAPI from your front end, MVC client.
In your controller page, remove anything that has to do with DbContext, Entity Framework, etc. The reason is by default, the controller will want to perform CRUD operations by calling the DbContext, and we don't want this. We want to call the WebAPI instead to do this. When I refer to "Controller", I'm referring to the MVC controller, not the WebAPI controller.
First and foremost, declare some member variables in your MVC controller. The rest of your MVC controller will utilize these:
HttpClient client = new HttpClient();
HttpResponseMessage response = new HttpResponseMessage();
Uri contactUri = null;
In your MVC controller, create a constructor for your controller, as such:
public ContactController()
{
// set base address of WebAPI depending on your current environment
// the URL below, if the API is in the same project, will be something
// like "http://server/YourProjectName" - replace server with either
// "localhost", etc.
client.BaseAddress = new Uri("http://server/YourAPI/");
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
Replace the Index action's code with something like the following. Note that the only relevant pieces are the client.GetAsync() call and the var contacts assignment. Everything else is not necessary for the context of this problem. The value inside the client.GetAsync() should be the name of your controller, prepended by any custom routing you set up in your WebApiConfig.cs - in my case, I added the api part in my route to distinguish between API calls and normal calls:
public ActionResult Index()
{
response = client.GetAsync("api/contact").Result;
if (response.IsSuccessStatusCode)
{
var contacts = response.Content.ReadAsAsync<IEnumerable<Contact>>().Result;
return View(contacts);
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Create action (the HttpPost action) with something like the following. Again, the only important piece is the client.PostAsJsonAsync() part - this is what calls the WebAPI's POST action which takes care of, in my case, inserting a new record into the database:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Contact contact)
{
// Create a new product
response = client.PostAsJsonAsync("api/contact", contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Edit action (the non-HttpPost action) with something like the following. This was a little tricky because in order to edit, you had to retrieve the record first, so basically, the HttpPost version of Edit will contain somewhat similar code, with an additional line of code that performs the edit POST (PUT). Below, we're getting the response from the WebAPI by passing it a specific record ID. So, just like for Index (GET), we are doing the same thing only passing in the ID so we only get back one record. Then, we cast the response to an actual object that can be operated on in the View:
public ActionResult Edit(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Replace the Edit action (the HttpPost action) with something like the following. Below, we're getting the record to be edited by calling client.GetAsync() and passing in the primary key as a parameter (contact_id). Then, we're getting the RequestUri from that response and saving it. Then, we're calling client.PutAsJsonAsync() and passing in the Uri.PathAndQuery (what we just saved) as well as the object to be edited.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Contact contact)
{
response = client.GetAsync(string.Format("api/contact/{0}", contact.contact_id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.PutAsJsonAsync(contactUri.PathAndQuery, contact).Result;
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
// add something here to tell the user hey, something went wrong
return RedirectToAction("Index");
}
}
Replace the Delete action (the non-HttpPost action) with something like the following. So again, we're getting the record from the database by simply calling client.GetAsync() and casting it to an actual object my app knows of.
public ActionResult Delete(int id = 0)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
Contact contact = response.Content.ReadAsAsync<Contact>().Result;
if (contact == null)
{
return HttpNotFound();
}
return View(contact);
}
Finally, replace the Delete action (the HttpPost action) with something like the following. Again, we're doing something similar to that of the Edit action. We are getting the record to be deleted, casting it to an object, and then passing that object into a client.DeleteAsync() call, as shown below.
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
response = client.GetAsync(string.Format("api/contact/{0}", id)).Result;
contactUri = response.RequestMessage.RequestUri;
response = client.DeleteAsync(contactUri).Result;
return RedirectToAction("Index");
}
I'm working on my first ASP.NET MVC 3 application and I've got two Controllers with Views, IceCreamController/IceCreamView and RecipeController/RecipeView, and on each the user can make a selection and display the recipe.
Selection causes a PartialView to be displayed which an Edit link on it. When clicked the EditView for this recipe is displayed, allowing the user to edit the attributes of the recipe item selected.
Great. This works fine except currently the POST action in the RecipeController looks like so:
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View();
}
and that ends up always showing the Index view for Recipe, which isn't what I want. Rather, I'd like to be able to do is send the user back to the appropriate View (IceCreamView or RecipeView) when they've submitted their changes.
I assume that others have done something similar to this. How do you communicate which Controller/Action should be redirected to when the Edit is done?
Note:
I added a bit above to clarify that I've got two separate Controllers (IceCreamController and RecipeController) and each has a View that can select and ultimately do a
#Html.Partial("_Recipe", model.recipe)
to display the details of a particular recipe. My problem is how to get the page redirected back to either IceCreamView or RecipeView by the Edit Action on RecipeController - essentially, how do I communicate where it should go since the recipe details could have been displayed by either path.
Solution Employed:
As you can read below in the comments to Darrin's answer, since I've got more than a single controller involved, a solution is to utilize the viewmodel to pass in the controller/action that should be redirected to following when the Edit post action is completed.
As I've got more than a single instance of this situation (arriving at an Edit page via multiple paths), I think creating a simple BaseViewModel to hold this functionality might be in order and then have all the applicable viewmodels inherit from that BaseViewModel.
I'm don't think it needs to be anything more than something like:
public BaseViewModel
{
public BaseViewModel(string controller, string action)
{
ControllerName = controller ?? string.empty;
ActionName = action ?? string.empty;
}
public string ControllerName { get; set; }
public string Action { get; set; }
}
And then a viewmodel's constructor could just be modified to pass in the controller/action and hand that off to the base class.
There may be other solutions to this and if so, I'd like to hear them.
[HttpPost]
public ActionResult Edit(RecipeViewModel viewModel)
{
// updates the underlying model with the viewModel
// other things not germane to the discussion
return View("IceCreamView");
}
or if you wanted to redirect you could have a controller action that would serve this view and then return RedirectToAction("IceCream"); which is probably more correct rather than directly returning a view from a POST action in case of success.
Let's say I have a Controller that handles a CRUD scenario for a 'Home'. The Get would look something like this:
[HttpGet]
public ActionResult Index(int? homeId)
{
Home home = homeRepo.GetHome(homeId.Value);
return Json(home, JsonRequestBehavior.AllowGet);
}
So far so good. Then I add a post action for adding new ones.
[HttpPost]
public ActionResult Index(Home home)
{
//add the new home to the db
return Json(new { success = true });
}
Awesome. But when I use the same scheme to handle puts (updating an existing home)...
[HttpPut]
public ActionResult Index(Home home)
{
//update existing home in the db
return Json(new { success = true });
}
We run into a problem. The method signatures for Post and Put are identical, which of course C# doesn't like. I could try a few things, like adding bogus parameters to the signature, or changing the method names to directly reflect CRUD. Those are hacky or undesirable, though.
What is the best practice for going about preserving RESTful, CRUD style controllers here?
This is the best solution that I know of:
[HttpPut]
[ActionName("Index")]
public ActionResult IndexPut(Home home)
{
...
}
Basically the ActionNameAttribute was created to deal with these scenarios.
HttpPut and HttpDeletes are restricted by some firewalls so at times simply HttpPost and HttpGet are used. If a record ID is passed in (or some other criteria) you know its an update. Granted - this is for you to determine, httpput may work just fine for you, this is just a warning on it, it usually isn't a big deal.
Either method used - beware of users trying to inject false IDs into the page in order to forcing updates of records they don't have access to. I get around this issue by hashing in this case home.HomeId on the view when we render it
ViewData["IdCheck"] = Encryption.ComputeHash(home.HomeId.ToString());
in your view:
<%: Html.Hidden("IdCheck", ViewData["IdCheck"]) %>
in your HttpPost or HttpPut method (whichever is doing the update)
if (Encryption.ComputeHash(home.HomeId.ToString()) != (string)Request.Form["IdCheck"])
{
throw new Exception("Hashes do not match");
}
Again - this same security issue exists no matter which method you use to do your update if you are trusting form data.
I'm developing a wizard type solution in MVC2 and I'd like to prevent users from going to step 2 directly, however, I'd still like it to show up in the URL.
Also, because progress can be saved at any time, I'd still like the ability to programmatically go to Step2. How can I do this?
[HttpGet]
public ActionResult Step1() {
return View("Step1View");
}
[HttpPost]
public ActionResult Step1(Stuff s) {
return RedirectToAction("Step2", new { S = s });
}
[HttpGet] //<-- how do I stop users going directly here
public ActionResult Step2(Stuff s) {
return View();
}
[HttpPost]
public ActionResult Step2(Stuff2 s) {
return RedirectToAction("Step3");
}
I haven't tried this myself but if I were to I'd give some consideration to ActionFilters. I'd create some context object that describes the wizard data and the steps through that wizard (maybe wrapping a model or two in some fashion).
This wizard context would be self 'validating' in the sense that I can ask what the next valid step is.
Then, with that, I load it up from the action filter and then if the current action is not valid for that step I redirect.
Of course, I can do that without the action filter and just have that as pre-amble to the method I'm looking at. Personally, I'd do this first, then play with action filters to try and make it look a little neater if I had time.
Action filters are the way to go
First of all this will heavily depend on the storage system of your temporary saved data. Action filter should check this store and see whether step 1 data exists. If it doesn't you can always add an error to ModelState thus make it invalid. Your Step 2 code would therefore look like this:
[HttpGet]
[CheckExistingData]
public ActionResult Step2(Stuff s)
{
if (!this.ModelState.IsValid)
{
return RedirectToAction("Step1");
}
return View(s);
}
So. Your filter should check for existing data and either:
fill up Stuff parameter or
add model state error and keep parameter null
Data consolidation
The way that you've written redirect to action (in step 1 POST) to just provide a complex object is not a good way of doing it. You should consolidate data storage, so no matter where your user came to step 2, filter would always work the same. In your Step 1 POST you should save data to this particular storage and just redirect to Step 2.
You have two scenarios of getting to Step 2:
From step 1
From anywhere after data has been saved
This way, your step 2 would work the same for both scenarios. And you could create an arbitrary number of steps in your wizard, and the same process would still work the same.
I have finished developing a highly reusable wizard that by just doing:
return Navigate();
from the actions, the wizard knows what to do (this is possible if you implement a wizard pattern). Navigate() being a method defined on a base WizardController class.
The reason this works is that, in essence, step info gets serialized to the page with each request (AJAX or not), and is deserialized when the controller reads the response in the OnActionExecuting method.
The framework uses WizardStep attributes to know which action corresponds to which wizard step, and the controller is decorated with a WizardOptions attribute that dictates how the Wizard will allow itself to be navigated. EG:
[WizardStepOptions(WizardNavigatorRules.LeapBackOnly, WizardButtonRules.Both, WizardCompleteRules.DisableNavigation)]
public class MembershipFormController : WizardController<ESregister.Models.TheSociety.RegistrationData>
{
[WizardStep(1, "Start")]
public override ActionResult Start()
{
return Navigate();
}
It works a dream. If in the course of your wizard's use, you need to prune or add steps, you just define which steps are to be displayed using the Range property, also defined on the base class WizardController:
[WizardStep(2, "Category")]
public ActionResult Category()
{
return Navigate();
}
[HttpPost]
public ActionResult Category(int ? Category)
{
if (Category == null)
{
ModelState.AddModelError("Category", "You must fill in a Category!");
return Navigate();
}
if (Category == 3)
{
Range = new List<int> { 1, 2, 7, 8 };
}
else
{
Range = DefaultRange();
}
return Navigate();
}
The wizard framework implements PRG automatically. You only need to provide HttpPost in a case like the above where you need to, for example, prune the steps range depending on user input.
It also provides navigation controls as follows:
<% StepManager stepManager = (StepManager)TempData["stepManager"];
Html.WizardNavigator(stepManager); %>
Html.WizardButtons(stepManager, WizardButtonLocation.Top); %>
Where the WizardNavigator shows / provides links to the different steps (links if allowed) and WizardButtons are the Start, Next, Continue, Previous and Confirm buttons.
It is working in production.
I have included all this detail to show what is possible and that the suggested solution does work.