ASP.NET MVC, finding if model has changes - asp.net-mvc

How do I use the modelstate in ASP.NET MVC to see if the user has made changes or not?
For example:
A form gets loaded and the user has the ability to make changes on the form.
After making changes, users clicks apply or cancel button.
Clicking the button brings control back to my controller where I do validation, then commit to database.
What if the user clicks the apply button without making changes? I want the controller to be able to detect that nothing was altered by the user and send the appropriate message.
Is there a way to detect this using modelstate? Or is there another way to do this?
Thanks

We use automapper to take our domain entities and map them to view models. Something like this:
var viewModel = Mapper.Map<DomainEntityClass, ViewModelClass>(entity);
Then, on post back of the view/page, we can test if they are different by loading the entity again and comparing it to the mapped view model:
var entity = Repository.Get<DomainEntityClass>(id);
var mappedEntity = Mapper.Map<DomainEntityClass, ViewModelClass>(entity);
// code to compare mappedEntity to incoming viewmodel
//
// or build an equality comparer to test them
The idea is that if the mapping configuration hasn't changed, the Mapper.Map call (given the same entity) should return matching view model objects if nothing has changed.
For help building equality comparers, check here - http://msdn.microsoft.com/en-us/library/ms132151.aspx

Related

Mvc How safe is viewbag?

My website is built around tabs. I have one single page with multiple partial views that display each tab.
The problem im facing now is I want to loop through files that the user has uploaded and display them in one of my partial views. This requires me to send the file list as a paramater in my action like this:
//Uploadedfiles is a function that adds the files to a list.
var files = UploadedFiles();
return View(files);
Because im only using one view to display all my partial views, i get:
The model item passed into the dictionary is of type 'Microsoft.WindowsAzure.Storage.Core.Util.CommonUtility+d__0`1[Delamapp.CloudStorageServices.UploadEntity]', but this dictionary requires a model item of type 'Delamapp.Models.LoginFolder'.
This means im required to not send a model item to my index view. Now, the only thing i can think off is adding my file list to viewbag and then display them on my view. BUT.. The files require high security. How safe is viewbag? Can you for example store sensitive login information in there? Can you think off some other way to accomplish this?
Thank you in advance
You can pass a model item to your view, but the model item you are passing doesn't match the type of model that your view uses (that's what the error message says).
So you need to do one of the following:
Modify your view to accept the model type that you are passing to it
Put the data you want to pass into the model type that your view is expecting
Create a new model type for both your data and for the view, and use that
In terms of security, I don't think using a view bag versus model binding really enters into the question of security. Both are just ways of passing data in between the controller and the view, and that all takes place within the ASP.NET process (perhaps you have ViewBag confused with Web Forms' ViewState?).

ASP MVC3 Pass additional params with "return View(model)"

I'm in serious need of passing url params with View class. Here's code:
if (!ModelState.IsValid)
{
return View(model);
}
This should not only return model based view, but also add specific param to URL (param won't change view details, but is needed as it's one of few automatically generated SessionKeys (one for each tab/window used to view app) and I know no other way to get to it, different than passing as param (it can't be generated everytime, 'cos params will change; it can't be global variable because it'll reset its value each refresh; it can't be static, because static is evul).
Oh this action is called with use of form and submit button, not actionLink or something like this.
EDIT1: I need params to stay in URL after refresh, or I need some other form of keeping data that persists through refresh/validation fail.
If I understand you correctly you have data that you need to use in generating Urls on your page? This just forms part of your ViewModel - or at least it should, since it's data that the View needs in order to render.
You can use ViewData to add any extra data that isn't part of your view model. Or, better still, add the data as members to it. Equally, if different views with different View Models require this data, add a ViewModel base class and derive from that so you can share that data.
use
RedirectToAction("actionName","controller",
new RouteValueDictionary(new {param1="value",param2="value2"});
or you can use hidden field to store the values in your page and then pass this down as and when you need them..

Code behind user control

How would one go about performing controller actions from withing an ASP.net MVC user control?
My scenario is that I have a userID and I want to transform it into a name from the database. To do this I've annotated my model with a display type and put a User template in the shared display templates but I'm not sure where I should write the code which does the lookup to convert from userID to user name.
I think that code ought to go into your models and you should be calling it in your controller and passing it to your user-control in a viewdata. This is if I understood your question.
I would just have the model expose the name and not the userID. This way your view (user control) is only displaying the name and not trying to do a DB lookup. Your "User Control" model would be responsible for how it gets the name, i.e. the DB from your question.
In short, you don't do that.
You should be passing the necessary data to the MVC user control from the View, which in turn should be getting it's information from the controller.
The view (or user control) should not have any knowledge of the controller. You may want to use RenderAction instead of a user control if you feel that the view shouldn't be responsible for passing the necessary information into the user control.

ASP.net MVC - How to persist model over various views

Situation: In some project management software written in asp.net I have a create project page (working fine). I need to add to this the ability to add tasks from a list of templates to this project pre-creation BUT the list of available tasks is dependent on some values sitting in the create form.
My abstract solution is this:
I have a "Create" view and an "Add Tasks" View - both strongly typed to a composite viewModel defined in the controller
My Create method checks which button was used to call it - if the
button was "Add Tasks" it then renders the AddTasks view, passing the model in from the create view, again all in the same controller.
The AddTasks View posts to the Create view with one of two buttons, one loads the view and the other causes an actually DB save.
My Problem is this:
The different views use different properties of the same model, but in passing this model between them, the data is reset (in any case reload or save).
I am guessing this is happening from auto binding of data - though I thought fields not present on the form would not overwrite existing model data passed down.
There is hardly any code in the controller manipulating the model at present - It is only passed from view to view in these cases.
This is the controller code:
// POST: /Project/Create/<viewModel>
[Authorize, AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id,id")] ProjectViewModel model)
{
if (model.SubmitValue == "Create")
{
try
{
model.Project.Id = Guid.NewGuid();
model.Save(this.User.Identity.Name);
return this.RedirectToAction("Details", new {id = model.Project.Id});
}
catch (Exception e)
{
this.ModelState.AddModelError(e.ToString(), e.ToString());
}
return View(model);
}
if(model.SubmitValue == "AddTasks")
{
return this.View("AddTasks",model);
}
return this.View(model);
}
//POST: /Project/AddTasks/ + model
[Authorize, AcceptVerbs(HttpVerbs.Post)]
public ActionResult AddTasks([Bind(Include = SelectedCarrierTasks")]ProjectViewModel model)
{
return View(model);
}
The Question is: How do I maintain the state of the model across these views until it finally save it?
I would prefer to avoid any hackish (TempData) or JS dependant solutions, but I am not closed to these if they are really the best solution.
Thanks,
Adam Tolley
One simple solution is to persist the ViewModel object in a Session variable and bind the View from this source.I ts certainly not the most elegant solution. Another option, and probably less elegant one is persist this model data in the database, with some temporary/unsaved flag.
The problem is that when you display the add tasks view you're not providing fields for your "Project" object therefore the ModelState loses the data related to the project, you will need to provide this fields to ensure you're not loosing that data.
You don't need to display this fields they can be of type hidden and they will preserve the value. Just make sure that if you will be binding to a view model you will need to name this fields correctly like this Model.Project.Property.
Perhaps I am trying to solve the wrong problem (ala Bruce Eckel). I am going to try to move to a structure that needs this sort of fuzzy boundary less. I don't want to adopt a REST paradigm only to shoe-horn it into a stateful application.
Possibly these controls belong on the same page, and I can use some JQuery goodness to put in a tab pane for easiness on the eyes.
Thanks to those who answered, I found each useful and will try to remember to up-vote them as soon as I have some more rep.
I can't comment on other peoples questions at the moment, but the only real option is the session if you want to persist an objects state during web requests, or serializing it and placing it in a hidden field.
Or a final option would be to change the way your pages work so you can save the object after each request...
If your using nHibernate then you might want look into the Conversations pattern, but this just essentially saves the nHibernate session into the asp.net session anyway...

Passing data from Controller to a User Control View with ASP.NET MVC

I have a View class (OrderView.aspx) which shows the details of an order (Account Name, Order Date) as well as a list of order lines via the Repeater control. Each order line is represented by a User Control View (OrderLineView.ascx) which shows the details of the order line (Item Name, Quantity, Price). I have a model object called Order which I use as the data source for all of this, which I pass as the model object for the view.
Each OrderLineView user control has a Save and a Delete button. I have the Save button posting the contents of a form within the OrderLine control to a Save method on the Controller and then RedirectToAction back to the same Order page (this refreshes the whole page, nothing AJAXy about it). I have the Delete button linking to a method on the Controller that tries to delete, and then RedirectToAction back to the same Order page. If the delete fails, however, I want a little error message to show up next to the delete button when the page renders again(remember, there is a delete button for every order line on the page, so I only want the message next to the one I clicked). My questions:
1 - How do I pass this data from my Controller method to the specific User Control? Should I somehow add it in to the model? Seems like a bad idea (since it really isn't part of the model).
2 - Should I have a OrderLineController for the OrderLine operations as well as a OrderController for Order operations? I just want to know if best practice is to have a separate Controller for every view.
3 - I have seen how some people might call RedirectToAction with an anonymous type value like this:
RedirectToAction("ViewOrder", new { Id = 1234, Message = "blabla"});
but this makes the Message value show up in the URL string. I am OK with that, but would prefer that it doesn't show if possible.
4 - Also, for accessing properties of the Model from within the view, I find myself doing this all of the time:
foo(((someModelType) this.ViewData.Model).SomeProperty);
I don't like this for a number of reasons, one of which is the fact that I don't want my view to be coupled with the type of my model (which is why I am using ViewPage instead of ViewPage). I would much prefer to be able to have a call like this:
foo(ModelEval("SomeProperty"));
Is there such a thing? I have written my own, but would like it if I didn't have to.
1
Check out ModelState.
ViewData.ModelState.AddModelError("something.Name", "Please enter a valid Name");
ModelState is actually a dictionary, so you could identify the errors on a per-control basis. I don't know if this is a best practice, but it would probably work.
Try something along the lines of
ViewData.ModelState.AddModelError("something#3.Name", "Please enter a valid Name");
and in your view, you could put
<%= Html.ValidationMessage(string.format({"something{0}.Name", YourUniqueId))%>
4
You can strongly type your view, so you don't need that cast, but if you're concerned about tightly coupling, this may put you off. But having the strong type there is no more tightly coupled than having a magic string point to that property of the model anyway. The former just gives you type safety and the glory that is intellisense.
Since your OrderLine has a unique ID you can use that to construct a key to be placed in the ModelState errors container.
public ActionResult Delete(int? Id)
{
ModelState.AddModelError("OrderLine" + Id.Value, "Error deleting OrderLine# " + Id.Value);
...
}
and then use the ValidatinoMessage helper. This will check the ModelState to see if an error exists and if it does it will display the message. Otherwise it's blank.
<%= Html.ValidationMessage ("OrderLine" + Id)%>
In the next release of MVC Model will become a top level property so the following
foo(((someModelType) this.ViewData.Model).SomeProperty);
can be written as
foo(Model.SomeProperty);
Model objects should already be typed unless you're using public object as a property?

Resources