Passing Information Between Controllers in ASP.Net-MVC - asp.net-mvc

This is a duplicate of How to RedirectToAction in ASP.NET MVC without losing request data
Hi, I have come into a problem which is making me scratch my head a little bit. Basically I have a login page Login.aspx , which has username and password fields, as well as an important little checkbox. The login is handled in the AccountController Login method. The code currently is as follows:
[AcceptVerbs(HttpVerbs.Post)]
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
Justification =
"Needs to take same parameter type as Controller.Redirect()")]
public ActionResult LogOn(string userName, string password, string returnUrl,
bool sendStoredInfo)
{
if (!this.ValidateLogOn(userName, password)) {
return View();
}
this.FormsAuth.SignIn(userName, false);
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl);
} else {
return RedirectToAction("Index", "Home");
}
}
Basically, if the line return Redirect(returnUrl); fires, then it will end up in another controller, the OpenIDController, and it is that situation where the sendStoredInfo bool becomes important. But the problem is I have no reference to it when I'm in the OpenIDController. How can I send this value across?

Change the call to:
return RedirectToAction("LoginFailed", new { sendFlag = sendStoredInfo });
The controller action method signature could be something like:
public ActionResult LoginFailed(bool sendFlag)
{
...
}

Also consider using TempData to pass data from controller to controller. This can be advantageous as you wont have to expose the bool sendFlag interface potentially to the user.
Code in the first controller:
TempData["sendFlag"] = sendStoredInfo;
return RedirectToAction("LoginFailed");
Code in the second controller:
public ActionResult LoginFailed()
{
bool sendFlag = TempData.ContainsKey("sendFlag")? TempData["sendFlag"]: false;
}

Because of the nature of redirects, you can only perform a GET operation.
This means that you have to pass the parameter as part of the query string.
So you would redirect to a url like http://host/dir/page?sendStoredInfo=true
Then, you can chose to have it part of your method signature in the other controller, or, you can choose to access it directly using the HttpRequest exposed by the HttpContext for the operation.
You can also call the RedirectToAction, as per this previous question:
How to RedirectToAction in ASP.NET MVC without losing request data

As far as my knowledge serves me well, four different methods exist to handle passing data between controllers in asp.net MVC. They are 1. ViewData 2. ViewBag 3. TempData and 4. Sessions. If you may like a relatively good explanation besides a downloadable sample, please take a look at here

Related

How to add WebApi in Asp.net MVC and then consume the WebAPI in the same application

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");
}

MVC _Layout with Action using Session -order of loading is an issue for me

All,
I have an MVC _Layout.cshtml that calls:
#Html.Action("GetActionStrip", "Vehicles")
I also have a Controller that loads some views.
My Issue is that I have a controller action called GetVehicleDetails which gets a vehicle by ID.
The Action on:
#Html.Action("GetActionStrip", "Vehicles")
requires that GetVehicleDetails loads first as it need to put a vehicle id into session.
This is not working as:
#Html.Action("GetActionStrip", "Vehicles")
Loads before GetVehicleDetails.
#Html.Action("GetActionStrip", "Vehicles")
Needs to be on multiple views, that's why I put it in the _Layout file.
I can get it to work by putting:
#Html.Action("GetActionStrip", "Vehicles")
On every view I need it on and then they load in the correct order. ie.. the controller action GetVehicleDetails sets the vehicle id into session and then:
#Html.Action("GetActionStrip", "Vehicles")
Reads the session value.
Has anyone got any idea if I can do it the way I want or will I have to put my #Html.Action on every view which kind of breaks the DRY principle.
thanks
RuSs
Paul, I tried to write you a comment but the character limit killed me. Here is my comment:
Paul,
Thanks for the message. I understand what you have written but before I continue and try to implement something like this I just want to be sure you understand, fully, the scenario.
Will YOUR scenario cater for the fact that the code that needs the session value is called from an #Html.Action in my _Layout (master page so to speak) whereas I need my controller get action to receive a parameter and set this parameter into session.
From what I understand, _Layouts (master pages) load first so my #Html.Action would run and look for the session value. But, as this code is in a _Layout, it would run first and hence the GET on my controller has not yet set the session from the actions passed in parameter.
Note: my _Layout doesnt have it's own controller (not sure if this matters)
Thanks
RuSs
Something about the design is fundamentally incorrect. You shouldn't have different components being tightly coupled like this. The order shouldn't matter for which one comes first.
Here is how I might do what you're looking for. I'd create a model bound class that you can receive in your controller actions where you need the session value. The model will pull the session value from the database or wherever if it hasn't been set yet otherwise it uses the session value. Now order doesn't matter. Better yet you could make MySessionObject an interface and then you can mock it out in your test cases.
public interface IMySessionObject
{
int GetValueX();
}
public class MySessionObject : IModelBinder, IMySessionObject
{
private HttpContextBase _httpContext;
private MySessionObject(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
public int GetValueX()
{
if (_httpContext.Session["x"] == null)
{
_httpContext.Session["x"] = 54; // Get the value here.
}
return (int)_httpContext.Session["x"];
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var context = controllerContext.HttpContext;
var obj = new MySessionObject(context);
return obj;
}
}
public class HomeController : Controller
{
public ActionResult Index(IMySessionObject obj)
{
ViewBag.X = obj.GetValueX();
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult About(IMySessionObject obj)
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact(IMySessionObject obj)
{
ViewBag.Message = "Your contact page.";
return View();
}
}
Thanks Paul. I think your answer is correct without Sitecore CMS being part if the scenario. Ill vote you up but still doesn't fix my issue. Here is my other post which explains it better.
I don't expect you use Sitecore. https://stackoverflow.com/questions/14867915/order-of-loading-layout-and-url-driven-action-is-opposite-to-a-standard-non-sit ill try to find a way for Sitecore NOT to declaratively load my _layout before my MVC code runs.

How does the controller receive parameters on HttpPost methods?

Take this snippet out of a controller, for example:
public ActionResult Login()
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
else
{
return View();
}
}
//
// POST: /User/Login
[HttpPost]
public ActionResult Login(LoginModel lm, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(lm.UserName, lm.Password))
{
FormsAuthentication.SetAuthCookie(lm.UserName, lm.RememberMe);
if (Url.IsLocalUrl(returnUrl) &&
returnUrl.Length > 0 &&
returnUrl.StartsWith("/") &&
!returnUrl.StartsWith("//") &&
!returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Username and Password combination is incorrect");
}
}
return View();
}
My question is, with the overloaded method of Login() (for the HTTP POST), what calls this method with the first parameter as a LoginModel class, and the second parameter as a string? My Login.cshtml view uses a submit button, so I am curious as to how these parameters get passed to the Login() method? What's to stop me from adding a third parameter? And how would that parameter get passed??
I know this is a basic question, I'm just trying to understanding the connecting piece between Views and Controllers.
This process is called Model Binding, lots of resources on it...I would start with How to implement a custom one because from that you will then understand how and why the process works.
http://buildstarted.com/2010/09/12/custom-model-binders-in-mvc-3-with-imodelbinder/
http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html
EDIT:
There is a default model binder which exists which takes items in the post collection like form data and url parameters and tries to match them up with items which you have told the controller to expect. It works something like this...
Request comes in,
MVC decides which controller should get the request
MVC then looks at the HTTP Method, was it a GET or POST?
If it was a post then it looks for the controller action with the HttpPost attribute
MVC then looks at the parameters you have defined...
In your case it says I have a LoginModel and a returnUrl;
MVC now is happy - it knows where things are going so now it looks at what you sent...
It looks at your post data (the form fields and query string parameters)
One by one it looks at their names and then tries to find a match in your controller parameters.
It finds a Username field in the form data, it takes a look at your parameters and asks if there is a Username? No, well maybe Username is a property of LoginModel...it then looks through LoginModel. Aha it says, found one. MVC then creates an instance of LoginModel and sets the Username property to the value you posted.
This process continues for each of the things it finds in your form and query string. There are a bunch of rules it follows but you get the picture - this matching is part of the 'convention over configuration' beauty of MVC.
This whole process above it model binding and since MVC implements this default behavior you do not see it written out in the Global.asax or anything, it is just part of MVC.
Take a look at your LogOn view, this is where the post method gets the returnUrl parameter
Html.BeginForm("LogOn", "Account", new { returnUrl = #Request.QueryString["ReturnUrl"] }

A smart way to handle Return URLs in an MVC environment

A problem I come up against again and again is handling redirection to the previous page after a user runs some action such as clicking a 'Back To ...' link or saving the record they are editing.
Previously whenever I have needed to know what page to return to, I would provide a returnURL parameter to my current view.
http://blah.com/account/edit/1?returnURL="account/index"
This isn't a very clean way of handling this situation, as sometimes the return URL contains parameters such as search strings, etc, which have to be included in the URL.
http://blah.com/account/edit/1?returnURL="account/index?search="searchTerm""
Also, this creates an issue when a user can go another page forward before coming back to the page with the returnURL because you have to pass the returnURL through all visited pages.
Simply calling the browser's Back functionality isn't really sufficient either, because you might want the page to refresh, e.g. to show the edits you just saved in the previous page.
So my question is, has anyone found a smart way to handle this kind of situation, specifically in an MVC environment?
Note: I am using ASP .NET MVC so if possible I'd like answers to pertain to that, however any ideas are welcome.
What's wrong with setting a cookie, or using a session variable? The only reason you wouldn't is if you don't control the page that calls into you, in which case your only options are query strings, post values, or referrer.
I thought I might add my answer to the question to see if others think it's a good idea.
I'm simply passing it through to the controller using TempViewData:
#{
TempData["returnURL"] = Request.Url.AbsoluteUri;
}
and then accessing it in a similar way to this (in my real version I check that the key is in TempData and that the returnURL is a real URL):
return Redirect(TempData["returnURL"].ToString());
If it needs to continue on past the first page change (i.e. Search page -> Edit page -> Edit Section page) I'm adding it again
TempData["returnURL"] = TempData["returnURL"];
Check my blog post on it: Using cookies to control return page after login on asp.net mvc 3
Just like #Mystere Man mentioned, you can just use a cookie or session for it. I went for cookies back when I had a similar situation a while ago.
Try register a new route of which the url is /{controller}/{action}/{id}/returnurl/{*url} and then use a RedirectToAction in the action that accepts url as a parameter
Request.UrlReferrer.AbsoluteUri
though i'd still argue that you shouldn't be creating your own "back" button.
Use an interceptor or an aspect:
Intercept each request in some fashion (e.g., a #Before aspect) and save the requested URL to the session, overwriting it each time
In your view layer, access that Session object as needed, in your case for the back link.
This kind of design allows you to always have the most recent request available if you want to use it. Here's an example to write an aspect / interceptor in .NET. Additionaly, PostSharp is a .NET aspect project.
At present, a quick and dirty method has eluded me... so I'm using a practical method.
On a conceptual level, the 'back-ability' of a page should be determined by the page that you're currently on. The View can infer this (in most cases) if the parameters captured in the Controller are passed to it via the ViewModel.
Example:
Having visited Foo, I'm going to Bar to view some stuff, and the back button should return to Foo.
Controller
public ActionResult Foo(string fooId) // using a string for your Id, good idea; Encryption, even better.
{
FooModel model = new FooModel() { fooId = fooId }; // property is passed to the Model - important.
model.Fill();
return View("FooView", model);
}
public ActionResult Bar(string fooId, string barId)
{
BarModel model = new BarModel() { fooId = fooId; barId = barId };
model.Fill()
return View("BarView", model)
}
ViewModels
public class FooModel
{
public string fooId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
public class BarModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
View (Partial) // No pun intended... or maybe it was. :)
Your BarView can now interpret from its model where it needs to go back to (using fooId).
On your BarView (using MVC2 syntax):
Back
You can use Html.ActionLink as well.
Alternatively:
You can inherit your ViewModels from a BaseViewModel, which can have a protected property returnURL. Set this where necessary.
Example:
On your ViewModel:
public class BarModel : BaseViewModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
returnURL = string.Format("/Foo?fooId={0}", fooId)
// Get info from Repository.
}
}
On View:
Back
Would this be better handled by partial actions that display without leaving the page and using JQuery to make a dialog/wizard workflow?
Then you only need to react to the 'Finish' button on the dialog to refresh the original view.
For the part of your question regarding "saving the record they are editing" I would think the post-redirect-get (PGR) pattern would apply to you.
This might be a good place to read about it if you are not familiar with it.
http://en.wikipedia.org/wiki/Post/Redirect/Get
http://blog.andreloker.de/post/2008/06/Post-Redirect-Get.aspx
Encode the returnUrl using Url.Encode(returnUrl) for inclusion in the URL.
When ready to redirect, use Url.Decode(returnUrl) and use the value for the actual redirect.

RESTful Controllers with Different Http Methods, But the Same Parameters

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.

Resources