Required ModelValidation just for new objects ASP.NET MVC - asp.net-mvc-3

I have this issue since yesterday.
In my User model I have a [NotMapped] called "ConfirmPassword". I don´t save it on the database but I use it on my Create form as a always to validate the data input for new users.
Since than, it´s ok. The problem is on my [HttpPost] Edit action. I should be able to edit some user's data without type and confirm the password. I use both Password and ConfirmPassword as a way to confirm the old password and informe the new one, if I wanna change the password. But, if I don´t, I leave them blank.
I have used already the code below to be able to pass the ModelState.IsValid() condition and it worked:
ModelState["Password"].Errors.Clear();
ModelState["ConfirmPassword"].Errors.Clear();
But, just before the db.SaveChanges(), as User user view model is considered, it has both properties empty and I got:
Property: ConfirmPassword Error: The field ConfirmPassword is invalid.
The question is: How could I skip de Required model validation when I want to update an object?
I read already about custom ModelValidations with classes extending ValidationAttribute and
DataAnnotationsModelValidator but I am not doing it right.
Any idea? How could I create a custom model validation that checks if the UserId property is null or not. It´s a nice way to check if I'm in Create or Edit action.
Thanks,
Paulo

Using the domain objects as your ViewModel will leads you to a condition of less scalability. I would opt for seperate ViewModels specific for the Views. When i have to save the data i map the ViewModel to the Domain model and save that. In your speciific case, i would create 2 ViewModels
public class CustomerViewModel
{
public string FirstName { set;get;}
public string LastName { set;get;}
}
And i will Have another ViewModel which inherits from the above class, for the Create View
public class CustomerCreateViewModel :CustomerViewModel
{
[Required]
public string Password { set;get;}
[Required]
public string ConfirmPassword { set;get;}
}
Now in my Get actions, i use this ViewModel
public ActionResult Create()
{
var vm=new CustomerCreateViewModel();
return View(vm);
}
and of course my View(create.cshtml) is now binded to this ViewModel
#model CustomerCreateViewModel
<h2>Create Csustomer</h2/>
//Other form stuff
Similarly for My Edit Action,
public ActionResult Edit(int id)
{
var vm=new CustomerViewModel();
var domainCustomer=repo.GetCustomerFromID(id);
if(domainCustomer!=null)
{
//This manual mapping can be replaced by AutoMapper.
vm.FirstName=domainCustomer.FirstName;
vm.LastName=domainCustomer.LastName;
}
return View(vm);
}
This view is bounded to CustomerViewModel
#model CustomerViewModel
<h2>Edit Info of #Model.FirstName</h2>
//Other form stuff
In your POST Actions, Map it back to the Domain object and Save
[HttpPost]
public ActionResult Create(CustomerCreateViewModel model)
{
if(ModelState.IsValid)
{
var domainCust=new Customer();
domainCust.FirstName=model.FirstName;
repo.InsertCustomer(domainCust);
//Redirect if success (to follow PRG pattern)
}
return View(model);
}
Instead of writing the Mapping yourself, you may consider using AutoMapper library to do it for you.

Related

When rendering a view the model is not updated only the viewbag

So.... I have an action in my controller that basically copies the current model and returns a new view based on that copy. To inform the user that this is a copy I append a message via the viewbag stating that it's a copy. All seemed to be working until I noticed that it's not the copy that is being used when rendering the new view instead it's the original, but the viewbag on the other hand is updated so the message is shown.
Hmm, don't know if that's understandable so I'll try to show what i mean with some pseudo-code as well:
Model
public class Model{
[ScaffoldColumn(false)]
[HiddenInput(DisplayValue = false)]
public Guid Id { get; set; }
public string Name { get; set; }
}
View
<input type="submit" name="Copy" value="#_("Copy")"/>
Controller
public ActionResult Copy(model) {
ViewBag.Message = _("This is a copy.");
var clone = model.Clone();
return View("Index", clone);
}
I'm having a real hard time trying to wrap my head around this so any help/tips/pointers are really appreciated.
Oh, I've stepped through the code several times to ensure that the clone is really a clone. The only thing that differentiates them is the Id property and that is the new one in the controller but when the view renders it's back to the old one.
You need to clear the ModelState collection before returning the clone because the HtmlHelpers prefer to reuse the posted data:
public ActionResult Copy(model) {
ViewBag.Message = _("This is a copy.");
var clone = model.Clone();
ModelState.Clear();
return View("Index", clone);
}
You can read more about this feature in this artice: ASP.NET MVC Postbacks and HtmlHelper Controls ignoring Model Changes.

Model not updated from view to controller

I'm trying to do that:
Create a Model, add it on a session and send it to the view.
Change Model fields on my view
Get the Model from session updated on my controller
The problem is that my model is never updated when I'm changing values on textboxes, I'm sure that I'm missing something with razor,
View:
#model MvcTestApp.Models.Car
<div class="b1">
<div class="b2">#Html.EditorFor(e => e.KM)</div>
<div class="b2">#Html.EditorFor(e => e.RegistrationNumber)</div>
</div>
#Html.ActionLink("Car", "sendCar")
Controller:
On SendCar, I would like to get the model updated.
namespace MvcTestApp.Controllers
{
public class CarController : Controller
{
public ActionResult Show()
{
var model = new MvcTestApp.Models.Car()
{
RegistrationNumber ="12345",
KM = "12345"
};
Session["temp"] = model;
return View("Show",Session["temp"]);
}
public ActionResult sendCar()
{
return View("Show", Session["temp"]);
}
}
}
Model:
namespace MvcTestApp.Models
{
public class Car
{
[DataType(DataType.Text)]
public string KM { get; set;}
[DataType(DataType.Text)]
public string RegistrationNumber { get; set;}
}
}
You need to make your sendCar controller to update the model. Currently, all the changes you do will only persist locally until you navigate away from the page. You need to post the changed model back to the server.
Take a look at the "Handling edits" part of this example to see how it can be done:
Asp.net tutorials
The way to do this is by wrapping your model details in a form with a submit function. Then in your sendCar method take in a Car object and the model binding will take care of setting everything on the new object.
If you're wanting to persist this (I assume this is just for testing purposes?) then perhaps make your car that you're returning in your show method a class variable.
You should read a beginner tutorial about ASP.NET MVC, which will explain you how to send data from a form to a controller, as it seems you are absolutely not aware of how to do this.
You are not missing 'something', you are missing all about sending data from forms to controllers.

MVC3 Razor - Models and Views

I have an action that creates a List and returns it to my view..
public ActionResult GetCustomers()
{
return PartialView("~/Views/Shared/DisplayTemplates/Customers.cshtml", UserQueries.GetCustomers(SiteInfo.Current.Id));
}
And in the "~/Views/Shared/DisplayTemplates/Customers.cshtml" view I have the following:
#model IEnumerable<FishEye.Models.CustomerModel>
#Html.DisplayForModel("Customer")
Then I have in the "~/Views/Shared/DisplayTemplates/Customer.cshtml" view:
#model FishEye.Models.CustomerModel
#Model.Profile.FirstName
I am getting the error:
The model item passed into the dictionary is of type System.Collections.Generic.List`1[Models.CustomerModel]', but this dictionary requires a model item of type 'Models.CustomerModel'.
Shouldn't it display the Customer.cshtml for every item in the collection in the Customers.cshtml?
Help!
I am not sure why you are calling a partial view like this. If it is a Customer Specific view, why not put it under Views/Customer folder ? Remember ASP.NET MVC is more of Conventions. so i would always stick with the conventions (unless abosultely necessary to configure myself) to keep it simple.
To handle this situation, i would do it in this way,
a Customer and CustomerList model/Videmodel
public class CustomerList
{
public List<Customer> Customers { get; set; }
//Other Properties as you wish also
}
public class Customer
{
public string Name { get; set; }
}
And in the action method, i would return an object of CustomerList class
CustomerList customerList = new CustomerList();
customerList.Customers = new List<Customer>();
customerList.Customers.Add(new Customer { Name = "Malibu" });
// you may replace the above manual adding with a db call.
return View("CustomerList", customerList);
Now there should be a view called CustomerList.cshtml under Views/YourControllerName/ folder. That view should look like this
#model CustomerList
<p>List of Customers</p>
#Html.DisplayFor(x=>x.Customers)
Have a view called Customer.cshtml under Views/Shared/DisplayTemplates with this content
#model Customer
<h2>#Model.Name</h2>
This will give you the desired output.
Your view is expecting a single model:
#model FishEye.Models.CustomerModel // <--- Just one of me
You're passing it an anonymous List:
... , UserQueries.GetCustomers(SiteInfo.Current.Id) // <--- Many of me
You should change your view to accept the List or determine which item in the list is supposed to be used before passing it into the View. Keep in mind, a list with 1 item is still a list and the View is not allowed to guess.

Need help to explain Readonly\ScaffoldColumn(false)

Please help with such a question and do not judge strictly because I'm a newbie in MVC:
I've got a model for storing names of users by ID in my DB
public class Names
{
public int NameId { get; set; }
public string Username { get; set; }
}
,
a conrtoller
[HttpPost]
public ActionResult EditforModel(Names Name)
{
if (ModelState.IsValid)
{
db.Entry(Name).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
adding and editing view
adding is working well, the question is about editing
I use
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend> legend </legend>
#Html.EditorForModel()
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
to edit my model.
when trying to go to this view I see an editor for both Id and Username, but if i fill Id - I've got error, because there is no Entry in DB with such Id.
Ok.Let's look for attributes to hide an editor.
[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not.
applaying it to my model I've got "0" id posting from my View.Try another attr.
[ReadOnly(true)] makes a field a readonly-field. But at the same time I've got "0" in posting Id.
Modifying a view I placed an editors for each field in model
#Html.HiddenFor(model => model.NameId)
#Html.EditorFor(model => model.Username)
but using it is dangerous because some user can post wrong Id throgh post-request.
I can't use [ScaffoldColumn(false)] with applying Id at [Httppost] action of the controller,by searching appropriate user-entry in DB, because the name was changed..
I can't believe #Html.HiddenFor is the only way out.But can't find one :(
As you mentioned "[ScaffoldColumn(false)] is something like a marker whether to render an editor for Id or not", and [ReadOnly(true)] means that this property will be excluded by the default model binder when binding your model.
The problem is that the HTTP protocol is a stateless protocol, which means that when the user posts the edit form to the MVC Controller, this controller has no clue which object he was editing, unless you include some identifier to your object in the request received from the user, though including the real object Id isn't a good idea for the reason you mentioned (that someone could post another Id).
A possible solution might be sending a View Model with an encrypted Id to the View, and decrypting this Id in the controller.
A View Model for your object might look like this :
public class UserViewModel
{
[HiddenInput(DisplayValue = false)]
public string EncryptedId { get; set; }
public string Username { get; set; }
}
So your HttpGet action method will be
[HttpGet]
public ActionResult EditforModel()
{
// fetching the real object "user"
...
var userView = new UserViewModel
{
// passing the encrypted Id to the ViewModel object
EncryptedId = new SimpleAES().EncryptToString(user.NameId.ToString()),
Username = user.Username
};
// passing the ViewModel object to the View
return View(userView);
}
Don't forget to change the model for your View to be the ViewModel
#model UserViewModel
Now the HttpPost action method will be receiving a UserViewModel
[HttpPost]
public ActionResult EditforModel(UserViewModel Name)
{
if (ModelState.IsValid)
{
try
{
var strId = new SimpleAES().DecryptString(Name.EncryptedId);
var id = int.Parse(strId);
// select the real object using the decrypted Id
var user = ...Single(p => p.NameId == id);
// update the value from the ViewModel
user.Username = Name.Username;
db.Entry(user).State = EntityState.Modified;
}
catch (CryptographicException)
{
// handle the case where the encrypted key has been changed
return View("Error");
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(Name);
}
When the user tries to change the encrypted key, the decryption will fail throwing a CryptographicException where you can handle it in the catch block.
You can find the SimpleAES encryption class here (don't forget to fix the values of Key and Vector arrays):
Simple insecure two-way "obfuscation" for C#
PS:
This answer is based on the following answer by Henry Mori:
Asp.net MVC 3 Encrypt Hidden Values

ASP.NET MVC AllowHtml bug or something I didn't use correctly

My model contains a string field called "longdescription" which gets the value of the tinymce editor's content
Public class ArticleModel:BaseModel{
[StringLength(8000, ErrorMessage = "Long description must be in 8000 characters or less"), AllowHtml]
public string LongDescription { get; set; }
}
Here is my controller code
[HttpPost]
public ActionResult AddEdit(ArticleModel model)
{
string buttonName = Request.Form["Button"];
if (buttonName == "Cancel")
return RedirectToAction("Index");
// something failed
if (!ModelState.IsValid)
{
}
// Update the articles
}
My problem is when I use Request.Form to access the post value, it's working fine without throwing "A potentially dangerous...." error, but when I use Request.Params["Button"], it threw that errors. Is something I am missing?
Thanks
Updated
Sorry the answer Adam gave doesn't really answer my question. Can anyone give more suggestion?
Ideally you shouldn't really be using either. Those are more Web Forms centric values even though they 'can' be used.
Either pass in a FormsCollection item and check it there using collection["Button"] or even better - your cancel button itself should probably just do the redirect. Why post when you do nothing but redirect?
In your view you can emit the url via Url.Action() and put that into your button's click handler (client side)
It is the HttpRequest.Params getter that is throwing this exception. This getter basically builds and returns a key/value pair collection which is the aggregation of the QueryString, Form, Cookies and ServerVariables collections in that order. Now what is important is that when you use this getter it will always perform request validation and this no matter whether you used the [AllowHtml] attribute on some model property or if you decorated the controller action with the [ValidateInput(false)] attribute and disabled all input validation.
So this is not really a bug in the AllowHtml attribute. It is how the Params property is designed.
As #Adam mentioned in his answer you should avoid accessing request values manually. You should use value providers which take into account things such as disabled request validation for some fields.
So simply add another property to your view model:
public class ArticleModel: BaseModel
{
[StringLength(8000, ErrorMessage = "Long description must be in 8000 characters or less")]
[AllowHtml]
public string LongDescription { get; set; }
public string Button { get; set; }
}
and then in your controller action:
[HttpPost]
public ActionResult AddEdit(ArticleModel model)
{
string buttonName = model.Button;
if (buttonName == "Cancel")
{
return RedirectToAction("Index");
}
// something failed
if (!ModelState.IsValid)
{
}
// Update the articles
}

Resources