I have troubles managing data fetched by users on my app.
So far, if my understanding of ASP.NET MVC is what I think it is, I cannot put fetched data in static fields. For example, it would be a bad idea to do this:
private static List<MyObjects> listObjects = new List<MyObjects> { get; set;}
public List<MyObjects> GetMyObjects (int _objectID)
{
using(MyDatabase database = new MyDatabase())
{
listObjects = (from o in database.MyObjects
where o.MatchingID == _objectID
select o).ToList();
return listObjects;
}
}
Because that would make the list of data available to all users on the web site. So I've read a few things and I thought that I needed to put things into MVC Session dictionary so that each user would browse through their own lists.
So I have these methods:
public List<MyObjects> GetMyObjects (int _objectID)
{
using(MyDatabase database = new MyDatabase())
{
List<MyObjects> listObjects = (from o in database.MyObjects
where o.MatchingID == _objectID
select o).ToList();
return listObjects;
}
}
[HttpGet]
public ActionResult SeeItemsByID(int? _objectID)
{
// Pre-validations to make sure that the ID is valid, then:
List<MyObjects> listObjects = GetMyObjects((int)_objectID);
Session["myObjects"] = listObjects;
return RedirectToAction("SeeItems");
}
[HttpGet]
public ActionResult SeeItems()
{
List<MyObjects> listObjects = Session["myObjects"] as List<MyObjects>;
if(listObjects == null)
{
return RedirectToAction("Home");
}
return View(listObjects);
}
[HttpGet]
public ActionResult SeeItemsCompact()
{
List<MyObjects> listObjects = Session["myObjects"] as List<MyObjects>;
if(listObjects == null)
{
return RedirectToAction("Home");
}
return View(listObjects);
}
So let's resume the process:
First the user clicks on a link which calls the SeeItemsByID action, then it is redirected to the SeeItems action. In this view the user may click on a link to see the result "compacted" for any reasons. However, in my app, when the SeeItemsCompact method is called right after, all the Session data is lost.
How? And how could I keep this data alive?
Is ID specific to the user? Is the list so big that this even makes sense? (it rarely does and if it does, you'll want to implement paging at the server to reduce the time of retrieval)
If it were me, I'd serialize the data to a file based on ID then when the user comes back and the session has expired, you don't need to re-retrieve the data. You can check the timestamp on the file and reload the data if the timestamp is too old.
Related
So, I have simple web application that takes data from api, deserializes it, inserts it into database and then displaying it in a table.
I don't know how to separate deserialisation and insertion from my Index() method, so every time I refresh the page it deserializes and inserts data again.
Controller:
public class HomeController : Controller
{
private DBContext db = new DBContext();
public string data = "...."; //here is the json data that i get from API
public ActionResult Index()
{
RootObj myData = JsonConvert.DeserializeObject<RootObj>(data);
foreach (var item in myData)
{
MyModel myItem = new MyModel
{
name = item.name,
symbol = item.symbol,
price = item.price,
};
db.MyItemsDB.Add(myItem);
db.SaveChanges();
}
return View(db.MyItemDB.ToList());
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
How can I separate deserialisation and insertion from Index() method?
Thanks for help!
I didn't think about that my json is changing, but it does and because of that I need to do the insertion into database with every page refresh. The solution is doing unique field in the database, so there will be no repeated data.
UPD: The field "name" of myItem must be unique, but I need to update data in other fields if the item with this name already exists, so I did this:
db.MyItemsDB.AddOrUpdate(m => m.name, myItem);
db.SaveChanges();
Assuming you want to do a database insertion per unique user, you could use a cookie.
If the cookie doesn't exist, the user didn't visit the site yet (or has deleted his cookies)
If the cookie exists, the user already visited and you don't do the database insertion.
It doesn't remove all the insertions, but it reduces them.
If(HttpContext.Current.Response.Cookies.AllKeys.Contains("myCookie") == false)
db.SaveChanges();
else
{
HttpCookie myCookie = new HttpCookie("myCookie");
myCookie.Value = "SomeInfo";
myCookie.Expires = DateTime.Now.AddDays(1d);
Response.Cookies.Add(myCookie);
}
I have a simple form which saves the following entity
public class TravelInfo{
public int ID {get;set;}
public string CentreCode {get;set;}
public DateTime TravelDate {get;set;}
}
I have the standard 2 create methods in my controller - 1 get 1 post and am using this viewmodel to get stuff into the view.
public class TravelInfoVM{
public TravelInfo TravelInfo{get;set;}
public IEnumerable<SelectListItem> Centres {get;set;}
}
Controller methods...
public ActionResult Create(){
var CentresList = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
TravelInfoVM = new TravelInfoVM(){Centres = CentresList};
return View(TravelInfoVM);
}
[HttpPost]
public ActionResult Create(TravelInfoVM model){
//the Centres part of the model at this point is empty
if(ModelState.IsValid){
//save
//redirect
}
//do i **REALLY** have to get it again as below, or can I hold on to it somehow?
model.Centres = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
return View(model);
}
the question is, do I really need to do a second round trip to the DB to get the list of Centres if the ModelState comes back as invalid? or is there a better/different way to persist this list across posts, until the user correctly enters the details required to save..? or do i have completely the wrong end of the stick..
Not without adding it to the session, which is an inappropriate use of the session. Otherwise, each request is a unique snowflake, even if it seems like it's the same because you're returning the same form with errors.
I wouldn't worry about this. It's just one query, and since it's the same query already issued previously, it will very likely (or at least could be) cached, so it may not actually hit the database, anyways.
One thing I would recommend though is abstracting the query so that your GET and POST actions will simply call a function that won't change, and if you need to make a change to how the select list is created, you just do it in one place:
internal void PopulateCentreChoices(TravelInfoVM model)
{
model.Centres = db.Centres.Select(c=> new SelectListItem {Text = c.Name, Value = c.Code}).ToList();
}
...
public ActionResult Create(){
var model = new TravelInfoVM();
PopulateCentreChoices(model);
return View(model);
}
[HttpPost]
public ActionResult Create(TravelInfoVM model){
if(ModelState.IsValid){
//save
//redirect
}
PopulateCentreChoices(model);
return View(model);
}
Can anyone help me? I am taking a session value in variable like
var user = Session["UserName"];
and by this i will get username of an employee who is logged in. Now I want designation of that employee so I wrote
var data=from u in db.EmployeeTabs.Where(p=>p.EmpName==user).Select(v=>v.Designation)
Now variable data will probably contain the designation of employee who is logged in. Now I want a condition, based on the condition I want to redirect the page so I want an if condition like
if(val(data)=="Receptionist")
then it should display a Register.cshtml page, so for that what should I write?
And where to write? Means in an controller? Or i should create some method in controller?
I would recommend reading this tutorial Making some assumptions about your code, I think you want something like
public ActionResult Index()
{
var user = Session["User"];
using (var db = new YourEntity())
{
var data = from u in db.EmployeeTabs.Where(p => p.EmpName == user).Select(v => v.Designation);
if (data == null)
{
return RedirectToAction("Register");
}
Switch(data.First().Designation)
{
case "Receptionist":
return RedirectToAction(Register);
}
}
return View();
}
public public ActionResult Register()
{
return View();
}
Lets imaging the we have model:
public class InheritModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string OtherData { get; set; }
}
We have a controller with View, that represents this model:
private InheritModel GetAll()
{
return new InheritModel
{
Name = "name1",
Description = "decs 1",
OtherData = "other"
};
}
public ActionResult Index()
{
return View(GetAll());
}
Now we can edit this in View, change some data and post in back to server:
[HttpPost]
public ActionResult Index(InheritModel model)
{
var merged = new MergeModel();
return View(merged.Merge(model, GetAll()));
}
What i need to do:
In view we have a reproduction of model
User change something and post
Merge method need to compare field-by-field posted model and previous model
Merge method create a new InheritModel with data that was changed in posted model, all other data should be null
Can somebody help me to make this Merge method?
UPDATE(!)
It's not a trivial task. Approaching like:
public InheritModel Merge(InheritModel current, InheritModel orig)
{
var result = new InheritModel();
if (current.Id != orig.Id)
{
result.Id = current.Id;
}
}
Not applicable. It's should be Generic solution. We have more than 200 properties in the model. And the first model is built from severeal tables from DB.
public InheritModel Merge(InheritModel current, InheritModel orig)
{
var result = new InheritModel();
if (current.Id != orig.Id)
{
result.Id = current.Id;
}
if (current.Name != orig.Name)
{
result.Name = current.Name;
}
... for the other properties
return result;
}
Another possibility is to use reflection and loop through all properties and set their values:
public InheritModel Merge(InheritModel current, InheritModel orig)
{
var result = new InheritModel();
var properties = TypeDescriptor.GetProperties(typeof(InheritModel));
foreach (PropertyDescriptor property in properties)
{
var currentValue = property.GetValue(current);
if (currentValue != property.GetValue(orig))
{
property.SetValue(result, currentValue);
}
}
return result;
}
Obviously this works only for 1 level nesting of properties.
Per topic, it seems that what you want is a sort of "change tracking" mechanism which is definitely not trivial or simple by any means. Probably, it makes sense to use any modern ORM solution to do that for you, does it?
Because otherwise you need to develop something that maintains the "context" (the 1st level object cache) like EF's ObjectContext or NH's Session that would be generic solution.
Also, there is no information on what happens at the lower level - how do you actualy save the data. Do you already have some mechanism that saves the object by traversing it's "non-null" properties?
I have a similar project experience, which made me thought a lot about the original design. Think the following question:
You have a view that representing a model, then users modified
something of the model in the view, all the CHANGES are posted to
server and the model is modified, and then it's saved to database
probably. What's posted to the server on earth?
An instance of InheritModel? No. You want the changes only. It's actually part of InheritModel, it's a InheritModel Updater, it's an instance of Updater<InheritModel>. And in your question you need to merge two models, because your Update method looks like:
public InheritModel Update(InheritedModel newModel)
{
//assign the properties of the newModel to the old, and save it to db
//return the latest version of the InheritedModel
}
Now ask yourself: why do I need a whole instance of InheritedModel when I just want to update one property only?
So my final solution is: posting the changes to the controller, the argument is something like a Updater<TModel>, not TModel itself. And the Updater<TModel> can be applied to a TModel, the properties metioned in the updater is assigned and saved. There shouldn't a MERGE operation.
I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.