I have a dropdown menu that when you select an option value submit the form, and to avoid repetitive database calls I am storing my non-sensitive object in a session.
private List<Employee> stafflist
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = new Employee(); }
}
private void RemoveStaff()
{
Session.Remove("stafflist");
}
however in my
[HttpPost]
public ActionResult index (...)
{
//why can't I get the list of staff like this?
ViewBag.staff=stafflist.Where(..).toList();
//is the below still needed? i thought i
//have a session variable declare above,
//and to avoid 30x repetitive db calls?
//also note when i include the below the code runs fine,
//however, if i take it out it doesn't. i would like to avoid repetitive db calls
stafflist=db.Employee.toList();
}
First of all, you should not prevent to query the database. Proper caching is hard to get right, and a database is perfectly capable of performing queries and caching data.
If you're absolutely sure you want to circumvent the database, and query clientside (i.e. in the controller) then you need to pull the entire staff list from the database at least once per visitor.
You could do that in the first GET call to this controller, assuming the user will always visit that:
[HttpGet]
public ActionResult Index (...)
{
var cachedStaff = db.Employee.toList();
Session["stafflist"] = cachedStaff;
}
Then in the POST, where you actually want to do the database query (again, consider letting the database do what it's good at), you can query the list from the session:
[HttpPost]
public ActionResult Index (...)
{
var cachedStaff = Session["stafflist"] as List<Employee>();
// TODO: check cachedStaff for null, for when someone posts after
// their session expires or didn't visit the Index page first.
var selectedStaff = cachedStaff.Where(..).ToList();
// the rest of your code
}
Then the property you introduced can be used as syntactic sugar to clean up the code a bit:
private List<Employee> CachedStaff
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = value; }
}
[HttpGet]
public ActionResult Index (...)
{
CachedStaff = db.Employee.toList();
}
[HttpPost]
public ActionResult Index (...)
{
// TODO: this will throw an ArgumentNullException when
// the staff list is not cached, see above.
var selectedStaff = CachedStaff.Where(..).ToList();
// the rest of your code
}
A session is unique for the current user and the current session. That means that when the user closes the browser, the session information is lost. The session is also lost if the session cookie is removed. Read about state management.
If you want to have a global staff list that is available for all users you need to use something else. Caching is the most common case then.
you probably have it already figured it out, just in case I leave here what it worked for me.
First you create a new session variable based on an object created (in this case the object usr will be empty):
User usr = new User();
Session["CurrentUSR"]=usr;
where you want to use the new object, you will have to cast the session variable and point it to a new object created in that particular page:
User usr= new User(); //at this point the usr object is empty, now you are going to replace this new empty object with the session variable created before
usr=Session["CurrentUSR"] as User();
In case you have a list, the best course of action would be to create a List<> of that particular object.
Related
I need feature that is something similar to Laravel's old input helper but in MVC 5.
https://laravel.com/docs/5.6/requests#old-input
If validation fails, I need to reload all my model data as it was in the previous request except those inputs where user entered something wrong.
The problem is that my form has many disabled inputs and fields that program is fetching within [HttpGet] method, and they're getting lost during submission. So I need to store them in session.
The code below seems to work but is there any more efficient and beautiful way to do so with a less amount of code within each controller?
[HttpGet]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(Guid guid)
{
var model = new EditTaskViewModel();
model.Guid = guid;
await model.GetTaskFromRemoteService(new UserInfo(User));
ControllerHelpers.DisplayAlerts(model, this);
TempData["OldModel"] = model;
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[Route(#"TaskManagement/Edit/{guid}")]
public async Task<ActionResult> Edit(EditTaskViewModel model, Guid guid, string submit)
{
model.Guid = guid;
if (ModelState.IsValid) {
await model.UpdateTaskInRemoteService(new UserInfo(User), submit);
ControllerHelpers.DisplayAlerts(model, this, "Task successfully updated");
if (model.ErrorCode == null)
return RedirectToAction("Edit", new { guid = model.Guid });
return RedirectToAction("Index");
}
if (TempData["OldModel"] != null) {
model = (EditTaskViewModel)TempData["OldModel"];
}
return View(model);
}
Using session state (including TempData) like this may break when you have multiple copies of the page open. You can work around this by generating a unique ID for the session key and storing it in a hidden field.
However, I would try to avoid using session altogether.
A simple approach is to use hidden fields to store the values that aren't sent to the server because they are in disabled fields.
A more robust approach is a separate class (or at least a private method) that knows how to setup your model for the first time and in transition (e.g. failed server validation). I call these classes "composers" and I describe the approach here.
Pseudocode for how an action method with a composer might look:
if( ModelState.IsValid ){
return Redirect();
}
var rebuiltModel = _composer.ComposeEdit( incomingModel );
return View( rebuiltModel );
I think the answer was quite simple. The shortest and easiest way is to populate the object from the database\remote service once more.
The fields that user entered whether they're valid or not will stay as they were before. The rest of them will load once again.
Due to a problem caused by having multiple forms on a single page, I used an AJAX call to a WebMethod to submit my form instead of using ASP controls. However, in doing this, the previous method I had used to create a new entry into my database no longer works because a WebMethod must be static.
I have authenticated my user already using ASPX authentication, and am trying to retrieve the username and ID of that user with codebehind. The user has already been authenticated on Page_Load, but it seems I cannot access this information through my WebMethod. Is this possible to do inside of a static WebMethod? Thank you for all of your help in advance!
[WebMethod]
public static void CreateJob()
{
Submit_Job();
}
public static void Submit_Job()
{
if (Page.User.Identity.IsAuthenticated)
{
try
{
string username = Context.User.Identity.Name;
}
catch
{
Context.GetOwinContext().Authentication.SignOut();
}
}
var manager = new UserManager();
var usernameDatabase = new ApplicationUser() { UserName = username };
usernameDatabase = manager.Find(username, "password here");
if (usernameDatabase != null)
{
IdentityHelper.SignIn(manager, usernameDatabase, isPersistent: false);
string jobTitle = Request.Form["jobTitle"];
using (var ctx = new CreateUserContext(ConfigurationManager.ConnectionStrings["myconnectionstring"].ConnectionString))
{
Job job = new Job()
{
job_title = jobTitle
};
ctx.Jobs.Add(job);
ctx.SaveChanges();
}
}
}
Edit:
There are errors for example with Page.User.Identity.IsAuthenticated -- Page, Context, and Request all appear that they cannot be static.
The specific error:
(An object reference is required for the non-static field, method, or property 'Control.Page') as well as with Context and Request.
Moving it from a simple comment
I had the same issue recently.
Luckily, whenever a user signs in our application, we store the user information encrypted into a session variable, so I retrieve that information, pass it to our user's class constructor, which decrypts it and I can use my logged in users info without a hassle.
So, my solution is to store the users info in the Session, but be careful what you store. Maybe serialize the users object and store in the session, then, whenever you need it
public void Page_Load()
{
// Retrieve authenticated user information
UserClass userObject = GetUserCredentials();
// Call a method that turns the authenticated user object into a string that contains the users session information. Given the sensivity of this information, might want to try to encrypt it or offuscate it. Store it in a session variable as a string
Session["UserContext"] = userObject.SerializeUser()
/* rest of the page code goes here */
}
[WebMethod(EnableSession=true)]
public static void CreateJob()
{
Submit_Job();
}
public static void Submit_Job()
{
// Lets get the authenticated user information through the session variable. Due to the static nature of the method, we can't access the Session variables directly, so we call it using the current HttpContext
string serializedUserInfo = )HttpContext.Current.Session["UserContext"].ToString();
// Let's create the users object. In my case, we have a overcharged constructor that receives the users serialized/encrypted information, descrypts it, deserializes it, and return a instance of the class with the deserialized information
UserClass userObject = new UserClass(serializedUserInfo);
// Do whatever the method has to do now!
}
On the subject of serialization, a quick google search with "c# object serialization" will bring you several good matches. XML and JSON are 2 of the most used kind of serialization, specially on web methods. Binary serialization is a good option to also obfuscate information of the logged in user
Am modifying the complex function which is already written where they are using the below code :
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
// where TestData() hits the DB and returns corresponding result
Values() is called multiple places in the same file where this will return by hitting the DB TestData() first time and from next calls it will directly return from ViewBag.
Is this is a good approach ?
What are all the alternative approach we have in MVC to handle this scenario ?As DB hit is a costly call we need to use some other techniques.
Thanks
You could either keep your data in session like this:
Session['*your session key*'] = TestData();
And then retrieve it like this:
var myData = Session['*your session key*'] as YourObject //cast it to an object if you need to.
Or you could use caching:
System.Web.HttpRuntime.Cache[cacheKey] = TestData
And retrieving:
var myData =System.Web.HttpRuntime.Cache[cacheKey] as YourObject
That code should ensure that you only touch the database the first time the method is invoked.
If the same data is used on multiple pages you could also have a lot at the Cache- or Session class.
If size of the data retrieved from database is not very big then you can use Cache
Otherwise you can store data in Session as well.
You have the options to keep the data like Session, Cache.
[OutputCache(Duration = 60)] // Caches for 60 seconds
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
MVC Model Binder
See Sample code
I have following method in my mvc controller:
[HttpGet]
public ActionResult UserProfile(String username)
{
var user = db.Users.Find(username);
return View(user);
}
This function returns View with user profile. But result of this is the same, regardless of changes in database.
When I debug it seems like db is not changing at all, while in other controllers everything works just fine.
EDIT:
Place when I make changes
public ActionResult ExecuteRetreive(String username, String ISBN)
{
if (IsValid(username))
{
var resBook = db.Books.Find(ISBN);
var resUser = db.Users.Find(username);
var resRentedBooks = (from rb in db.RentedBooks
join b in db.Books on rb.ISBN equals b.ISBN
where b.ISBN == ISBN
where rb.Login == username
where rb.Returned == null
select rb).FirstOrDefault();
if (resRentedBooks == null)
{
return RedirectToAction("Fail", "FailSuccess",
new { error = "" });
}
resRentedBooks.Returned = DateTime.Now;
resBook.IsRented = false;
resUser.RentedBooks--;
db.SaveChanges();
return RedirectToAction("Success", "FailSuccess");
}
else
{
return RedirectToAction("Fail", "FailSuccess",
new { error = "Niepoprawna nazwa użytkownika" });
}
}
Im new to this so dont laugh at my code :P When I display resUser.RentedBooks--; it is the same every time.
As a follow up to what #JeroenVannevel said in the comments, another problem that you might be having because you're using a static context (and one that I've had to deal with in the past) is that once a specific DbContext has loaded an entity (or a set of entities, in my case) it won't tend to refresh just because some outside changes were made in the database. It loads those entities into Local and just refers to those automatically if you query for it.
The solution, then, is to always put your DbContext calls wrapped up in a using block, since DbContext implements IDisposable.
One word of caution with this approach, since you're using MVC: If you are using lazy loading, and you know that your View will need some information from a child object (or to list the names of a collection of child objects), you will absolutely need to hydrate those child entities before you get out of the using block, or you will find yourself getting exceptions saying that your context has been disposed.
So I'm have a really hard time figuring out when I should be attaching to an object and when I shouldn't be attaching to an object. First thing's first, here is a small diagram of my (very simplified) object model.
In my DAL I create a new DataContext every time I do a data-related operation. Say, for instance, I want to save a new user. In my business layer I create a new user.
var user = new User();
user.FirstName = "Bob";
user.LastName = "Smith";
user.Username = "bob.smith";
user.Password = StringUtilities.EncodePassword("MyPassword123");
user.Organization = someOrganization; // Assume that someOrganization was loaded and it's data context has been garbage collected.
Now I want to go save this user.
var userRepository = new RepositoryFactory.GetRepository<UserRepository>();
userRepository.Save(user);
Neato! Here is my save logic:
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
//DataContext.Organizations.Attach(user.Organization);
DataContext.Users.InsertOnSubmit(user);
}
else
{
DataContext.Users.Attach(user);
}
DataContext.SubmitChanges();
// Finished here as well.
user.Detach();
}
So, here we are. You'll notice that I comment out the bit where the DataContext attachs to the organization. If I attach to the organization I get the following exception:
NotSupportedException: An attempt has been made to Attach or Add an
entity that is not new, perhaps having
been loaded from another DataContext.
This is not supported.
Hmm, that doesn't work. Let me try it without attaching (i.e. comment out that line about attaching to the organization).
DuplicateKeyException: Cannot add an entity with a key that is already
in use.
WHAAAAT? I can only assume this is trying to insert a new organization which is obviously false.
So, what's the deal guys? What should I do? What is the proper approach? It seems like L2S makes this quite a bit harder than it should be...
EDIT: I just noticed that if I try to look at the pending change set (dataContext.GetChangeSet()) I get the same NotSupportedException I described earlier!! What the hell, L2S?!
It may not work exactly like this under the hood, but here's the way I conceptualize it: When you summon an object from a DataContext, one of the things Linq does is track the changes to this object over time so it knows what to save back if you submit changes. If you lose this original data context, the Linq object summoned from it doesn't have the history of what has changed in it from the time it was summoned from the DB.
For example:
DbDataContext db = new DbDataContext();
User u = db.Users.Single( u => u.Id == HARD_CODED_GUID );
u.FirstName = "Foo";
db.SubmitChanges();
In this case since the User object was summoned from the data context, it was tracking all the changes to "u" and knows how to submit those changes to the DB.
In your example, you had a User object that had been persisted somewhere (or passed from elsewhere and do not have it's original DataContext it was summoned from). In this case, you must attach it to the new data context.
User u; // User object passed in from somewhere else
DbDataContext db = new DbDataContext();
u.FirstName = "Foo";
DbDataContext.Users.Attach( u );
db.SubmitChanges();
Since the relationship between user and organization is just a GUID (OrganizationId) in your data model, you only have to attach the user object.
I'm not sure about your scaffolding code, but maybe something like this:
private const Guid DEFAULT_ORG = new Guid("3cbb9255-1083-4fc4-8449-27975cb478a5");
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
// via a GUID, not by assigning an
// Organization object
DataContext.Users.InsertOnSubmit(user);
}
else
{
DataContext.Users.Attach(user);
}
DataContext.SubmitChanges();
}
So "attach" is used when you take an object that exists from the database, detach it (say by marshalling it over a webservice somewhere else) and want to put it back into the database. Instead of calling .Attach(), call .InsertOnSubmit() instead. You're almost there conceptually, you're just using the wrong method to do what you want.
I used an big table with 400+ columns. No way am I going to map and test all that!
Get the original object from database, and attach it with the amended object. Just make sure the object coming back in is fully populated other wise it will override it the DB with blanks!
Or you can copy the original GET into memory and work on a proper copy (not just reference) of the MOdel, then pass the original and the changed one in, instead of re getting like I do in the example. This is just an example of how it works.
public void Save(User user)
{
if (!DataContext.Users.Contains(user))
{
user.Id = Guid.NewGuid();
user.CreatedDate = DateTime.Now;
user.Disabled = false;
user.OrganizationId = DEFAULT_ORG; // make the foreign key connection just
// via a GUID, not by assigning an
// Organization object
DataContext.Users.InsertOnSubmit(user);
}
else
{
var UserDB = DataContext.Users.FirstOrDefault(db => db.id == user.id); //Costs an extra call but its worth it if oyu have 400 columns!
DataContext.Users.Attach(user, userDB); //Just maps all the changes on the flu
}
DataContext.SubmitChanges();
}