variable set null after method - c#

I'm building a web site, and I need to show a list of customers. When a customer is selected, to show his items, I send the id of the customer from the view with this:
<th>
#using (Html.BeginForm("SetID", "Client", FormMethod.Post, new
{ id = item.id.ToString() }))
{
#Html.Hidden("id", item.id.ToString());
<input type="submit" value="see items" />
}
</th>
I receive the id in the controller and save it to make the query and show the values this way.
private string customer_id
[HttpPost]
public ActionResult SetCustomer(string id) {
Save(id);
return RedirectToAction("Index");
}
private void Save(string id) {
this.customer_id = id;
}
But when I get redirected to the Index view, the variable "customer_id", is null. Is there something I'm missing?

Because you're not persisting the value anywhere.
HTTP is stateless. What this means in ASP.NET is that each HTTP requests results in a new instance of the controller being requested. So the following sequence of events is happening here:
User makes a request to SetCustomer, creating a new instance of the class
Value is stored in a class-level variable
Request is responded to and completed, the instance of the class is destroyed
User makes a request to Index, creating a new instance of the class
Since it's a new instance, no value is set. There are a variety of places you can store data, it just has to be in a context that both requests can access. Examples, in no particular order, include:
Database
Session
URL query string
Cookie
etc.
Basically, you have to write the value to some location which persists between requests. In-memory variables don't do that in web applications.
A simple example here could be to include the value on the query string in the redirect. In ASP.NET MVC, that might look something like this:
return RedirectToAction("Index", new { customer_id = id });
What this would do is include on a URL parameter a customer_id value when the user is redirected. So your Index action you could accept that parameter:
ActionResult Index(int? customer_id = null)
{
// use the customer id if one is provided
}
In this case I assumed that the value should be nullable in case the Index is ever requested without a value. But what you prefer to do for that is up to you. Basically this action now has an optional parameter, which you would use however you're currently trying to use it.
The benefit of this is that it maintains the intended statelessness of web applications. You're storing the state (the customer_id value) in the request/response itself, as opposed to some other medium (session, database, etc.) where you become responsible to maintaining it.

ASP.NET MVC controllers are instantiated to serve every request. Your local variable this.customer_id is not supposed to survive a call to the server. If you want to save something, it should go in a database table, or a file or somewhere persistent.
You could also store it in memory like in a cache, but that would need to be in a reference to something that lives longer than the controller (which will be thrown away after the server responds back to the browser).
If you want something like I described to be long lived, you might need to use a service to inject into your controllers. Many people would use a DI or IOC framework and life cycle configurations to accomplish this.

Related

Passing data between Controller Actions

Hi I cant find the way to pass data between Action methods within one controller. The simplified code example is as follows:
public class HomeController : Controller
{
int latestID;
// GET: Home
public ActionResult Index()
{
latestID = 50000;
return View();
}
// GET: Generate
public ActionResult Generate()
{
// using the latestID here
return View();
}
}
Index loads when the app starts and latestID is set to 50000. But when I click button mapped to API GET request Generate, the latestID is suddenly null. I tried to implement TempData and Session but with no luck. Any help would be greatly appreciated!
I feel there are 2 possibilities here i.e. the value to be retrieved is for a user session or its global across all the users using the website.
If the value is user specific then it can be managed through session stage management like Viewbag. Or if this value is expected to be maintained on the server side alone then it needs to be retrieved again through some persistence mechanism like database or memory cache.
If the value is common across all the users using the Website then it can be achieved through the Dependency Injection of a singleton object (this can be a database manager or cache manager again or a simple in memory object). The static objects can be used as well but it wouldn't be ideal as the application wouldn't support horizontal scalability across instances.

Maintain the model lifetime in MVC application

I am new to MVC and I have very simple problem.
When user login to my application I need to create a specific object (model) for the user for eg UserObject.
This object is unique to current logged in user and should only be disposed when user click on logout.
I don’t know how to maintain the lifetime of the object. As if I create object in Action method of controller class then as soon as the request is finished I lose the reference of the object.
How this should have been done?
The lifetime of your models are only going to be as long as the request. So each time the user goes to another page or refreshes, the MVC framework is going to instantiate a new controller (and model within). Otherwise your server would have a ton of static objects floating around in memory which would use up a lot of resources and wouldn't scale.
In order to manage state, you are going to need to use other methods such as sessions/cookies and a database.
So let's say the user logs in via /User/Login. This routes the request to an action named UserController.Login().
Inside this action, it instantiates a UserModel.
public ActionResult Login(string username, string password) {
var userModel = new UserModel();
if (userModel.Authenticate(username, password)) {
// Setup your session to maintain state
Session["username"] = username;
} else {
return View("Login");
}
return View("LoginComplete");
}
You might want the user model to actually create the session, but I've shown it here for clarity.
The user model authenticates the user, and then you create a session just like you would in a traditional non-MVC site.
Then in subsequent requests, you will want to authorize the user, and use any session data you have to retrieve state information.
public ActionResult SuperSecretPlace() {
var userModel = new UserModel();
string username = Session["username"]
var user = userModel.GetUserByUsername(username);
if (user == null) throw new HttpException(401, "User is not authorized.");
return View("SuperSecretPlace", user);
}
In the action above, the UserModel might do something like query a database to retrieve the user's data so you can pass it in to the corresponding view.
If you want to make life easier, you might want to just use .NET's built in forms authentication:
http://www.codeproject.com/Articles/578374/AplusBeginner-splusTutorialplusonplusCustomplusF
For more info about the lifecycle of MVC:
http://www.dotnet-tricks.com/Tutorial/mvc/TbR0041112-Asp.net-MVC-Request-Life-Cycle.html
http://www.asp.net/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5-application
Actually what you are trying to achieve is passing model from controller to controller which is not possible. When an action is executed the context of the model object is disposed at the view and it can cannot be passed from controller to controller. You have to create a new object repopulate it and use it to achieve the goal in different controller.If you need the data to be persisted you can use sessions but still you need to create an object of the model in every controller.
The following image is for your reference as to see what to use when passing data between model-view-controller. Please feel free to ask if you need more information on this.
As opposed to the other aswers I would not use session as it has quite some disadvantages (scalability, pessimistic concurrency which blocks concurrent calls, app pool recycling...). Why you should not use session is documented in a lot of places like here or here.
Instead, I would store it in a cookie.
However, be sure to not store confidential or sensitive data. Whatever you use (cookies or session), it can be tampered with or stolen. If you are dealing with sensitive information, you need other solutions. Read also more about secure cookie solution here.

Pass complex object with redirect in ASP.NET MVC?

Hi,
I have a action that looks like this :
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(AdRegister adRegister, IEnumerable<HttpPostedFileBase> files)
The AdRegister is a complex class and I need to pass this in to a redirect method further down in the Register action, like this :
return this.RedirectToAction("Validate", adRegister);
The Validate action looks like this :
public ActionResult Validate(AdRegister adRegister)
I do know that I can pass simple parameters but in this case it´s a complex object. This example do not work, the adRegister´s properties will be null.
Is this posible and if so, how?
BestRegards
More Information : Register action will take the adRegister and do som magic on it, then It will be sent to the Validate action. The Validate action will return a validation page to the user. When the user hit the grant button the adRgister will be filled from the form and then sent to the vValidate post where it will be saved. I have looked in to place the adRegister in cache or database temporarily but it will be better if I could simple pass it to the next action.
One possibility would be to pass the simple properties in the query string:
return RedirectToAction(
"Validate",
new {
foo = adRegister.Foo,
bar = adRegister.Bar,
... and so on for all the properties you want to send
}
);
Another possibility is to store it in TempData (for the lifetime of the redirect) or Session (for the lifetime of the ASP.NET session):
TempData["adRegister"] = adRegister;
return RedirectToAction("Validate");
and then retrieve it from TempData:
public ActionResult Validate()
{
adRegister = TempData["adRegister"] as AdRegister;
...
}
Yet another possibility (and the one I would recommend you) is to persist this object in the POST method in your datastore:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(AdRegister adRegister, IEnumerable<HttpPostedFileBase> files)
{
...
string id = Repository.Save(adRegister);
return RedirectToAction("Validate", new { id = adRegister.Id });
}
and then fetch it from the data store after you redirect:
public ActionResult Validate(string id)
{
AdRegister adRegister = Repository.Get(id);
...
}
an idea would probably create a session variable and pass around a Key that references that session variable if the object is required acorss a few views?
ASP.NET MVC's tempdata should be perfect for this.
That said, TempData or Session is one option, but has some downsides like being quite violate and oftentimes murky or difficult to debug. What might be preferable is to "stash" the temporary value in a persistent store, such as the user's profile or your own database, then pass a key through the validate method which can then load the data from said store. This also opens up the possibility of recovering abandoned carts and such.

MVC Routes - How to get a URL?

In my current project we have a notification system. When an oject is added to another objects collection, an email is sent to those who are subscibed to the parent object. This happens on the object layer and not in the View or Controller.
Here's the problem:
Although we can say who created what with what information in the email, we cannot embed links to those objects in the email because in the object layer there is no access to a UrlHelper. To construct a UrlHelper you need a RequestContext, which again does not exist on the object layer.
Question:
I want to make a helper class to create the url's for me. How can I create an object that will generate these urls without a request context? Is it possible?
The problem is compounded by the fact that you don't want a relative URL in an email, you want an absolute email so you need to hard-code the domain too because there is no request to grab it from.
Another factor is that emails can outlive the current site structure by months or years so you need a kind of permalink, and thus a way to associate multiple Urls with a single action (additional routes). This latter issue is also a factor in SEO where you don't want to leave any page behind.
For now a static method on your controller UrlToActionX(params) sitting next to the method ActionX seems like the simplest workaround. All it does is the appropriate string.Format(...) on the id's of the strongly-typed parameters to generate the permanent Url. Add a static domain on the front, or a domain from the user object (since you know which domain they visit when they come to your site) and you have your email link.
It's not ideal but at least you now have only one place to maintain the Url generation.
IMHO: When it comes to permanent links to a changing web site sometimes it's better to rely on "configuration over convention". :-)
I'm not aware of a way to do this, you MUST have access to the routes at the very least to make your own helper. Unless your business objects know about the registered routes, you can't get away from doing some hard-coding.
Here is how you might limit the hard-coding of urls though...
Code in a url with all the relevant bits in your object's methods..
class Event
{
public void SendEmail()
{
var url = string.Format("http://myurl.com/r/Event?eventId={0}", EventId);
//send emails...
}
}
Note the /r/Event piece of the url. This would be a map to a RController that would be responsible for taking arbitrary, made-up links and sending a 301 Permanent Redirect and going through the route engine to create a real url using the current routes. This way you are only hard-coding a utility controller url and not to the ever evolving controller actions of your real pages.
class RController : Controller
{
public ActionResult Event(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Details", "Event", new { eventId = eventId });
return null;
}
public ActionResult Register(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Register", "Event", new { eventId = eventId });
return null;
}
}
It just feels a bit better than hard-coding a bunch of different controllers/actions that you might decide to rename later. Think of it as your own little TinyUrl like service.
You could define an interface with a method that takes whatever information is necessary to create a URL (object ids or whatever) and returns a URL. Write an implementation of that interface that uses the UrlHelper to do this work, and then supply this to your object layer (ideally with an IoC container).
You could use:
VirtualPathUtility.ToAbsolute(string.Format("~/r/Event?eventId={0}", id))
to resolve the url. Still not nice though.

MVC - Passing Data with RedirectToAction()

I'd like to take data entered in an MVC user form and display it in a different view.
The class has the following private variable:
IList<string> _pagecontent = new List<string>();
The following action accepts a FormCollection object, validates it, and passes it on to the "Preview" view as a List:
[Authorize(Roles = "Admins")]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateContent(FormCollection collection)
{
if (ModelState.IsValid)
{
string PageToInsert = collection["PageToInsert"];
string PageHeader = collection["PageHeader"];
string PageBody = collection["PageBody"];
//validate, excluded...
_pagecontent.Add(PageToInsert);
_pagecontent.Add(PageHeader);
_pagecontent.Add(PageBody);
}
return RedirectToAction("Preview", _pagecontent);
}
The Preview view has the following Page Directive for passing a strongly typed object List:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Template.Master" Inherits="System.Web.Mvc.ViewPage<List<string>>" %>
I would expect to be able to use the Model object to get my data, but alas I cannot. At the following line, I get an error index out of bounds exception, stating the index must be non-negative and less than the size of the collection:
<% if (Model[0].ToString() == "0") { %>
And some strange parameters have been added to the URL, as it resolves to
http://localhost:1894/Admin/Preview?Capacity=4&Count=3
So I have two questions:
When I call RedirectToAction and pass it my List, why is it inaccessible in the view's Model object?
What is the proper way to go about doing what I'm trying to do, namely pass a collection of strings to a view for display there?
Try using TempData. It is like a single-shot session object. You put values you want into TempData, immediately redirect and get them out. There is a good writeup here: http://blogs.teamb.com/craigstuntz/2009/01/23/37947/
Be careful when using TempData. It works great in a single server environment but in a cloud environment it may not work as expected since you cannot guarantee that the request will hit the same machine. This happens because TempData rely on the asp.net session. But if you are using other session manager like SQL or AppFabric Cache it will work fine.
The second parameter to RedirectAction is routeValues, not model.
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
Try using TempData for the model. Its for persisting data between redirects.
The problem with RedirectToAction is it's returning a HTTP 302 and the browser is then on it's own going and doing a brand new HTTP request. You may want to consider using a cookie and/or session object to persist the data between requests.
This is not working because RedirectToAction is actually sending back a Http 302 to the browser. When the browser receives this 302, it does a new request to the server asking for the new page. New request, new temp variables.
You will also face this problem when you try to save/edit/delete something and for some reason you deny it and you have to return the old form again.
So, instead of:
return RedirectToAction("Preview", _pagecontent);
Put the Preview logic in a separate method and just call it:
return PreviewLogic(_pagecontent);
You can also use the TempData[] dic to persist data for the next request like others have said, but then you will not avoid the 302 additional round trip to the server.
It sounds like you're trying to do:
public ActionResult UpdateContent(FormCollection form) {
...
return View("Preview", _pagecontent);
}
Note that a redirection is supposed to be a "clean slate" for the browser (except for things like the auth cookie). You don't get to tell the browser to pass information along to the next request, since the next request should be able to stand on its own. All you get to do is tell the browser what URL to request next. In ASP.NET MVC, when you pass an arguments-object to RedirectToAction, the public properties of that object are appended as query-string parameters to the generated URL.
Can't you just make 2 action results with the same name and mark 1 of them with HttpPost?
public ActionResult UpdateContent(FormCollection preview = null)
{
return View(preview);
}
[HttpPost]
public ActionResult UpdateContent(FormCollection collection = null, bool preview = false)
{
if (preview)
return UpdateContent(collection);
else
return UpdateContent(null);
}
It looks like you are looking for the UpdateModel command:
Check out ScottGu's blog post on the topic:
Improved UpdateModel and TryUpdateModel methods

Categories

Resources