MonoRail BindObject() equivalent in ASP MVC3? - asp.net-mvc-3

I need to do late binding of a complex type with DataAnnotations within an Action if condition X is true. I cant bind everything up front in the method params as a couple of them will not exist unless X == true so Model.IsValid will be false (since it tried to bind non existant params) due to validation failing on the complex type.
MonoRail solved this by allowing you to manually bind when needed, this is the exact scenario i have so im wondering if theres something similar available in MVC3?
I cant overload the Action as it blows up with an ambiguous call, i cant post to a different action as the form is all dynamic content, so i see the only alternative is rolling my own validation / binding mechanism pulling out data annotations to validate with.... boooo :(

I think what you need is the ControllerBase.TryUpdateModel method (it has a lots of overloads).
You can use it similarly like BindObject:
Some model:
public class MyModel
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
In the controller action:
[HttpPost]
public ActionResult UpdateModel(bool? acceptedConditions)
{
var model = new MyModel();
if (acceptedConditions ?? false)
{
if (TryUpdateModel(model))
{
//Do something when the model is valid
}
else
{
//Do something else when the model is invalid
}
}
return View();
}

Related

Required ModelValidation just for new objects ASP.NET MVC

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.

mvc3 composing page and form element dynamically

I'm developing an MVC3 application and I have a page (well, a view) that let the users edit document's metainfo (a classic #Html.BeginForm usage). For general documents users will see standard fields to fill up, but through a dropdownlist they will be able to specify the type of the document: this, through an ajax call, will load new fields on the edit-document-form.
Whem the user submit the completed form, at last, the controller should read all the standard fields, plus all the fields loaded as being specific to the type of document selected.
Question is, how can I handle all this extra fields in a controller?
Say that I have Document class and a bunch of other classes extendinf Document, like Contract : Document, Invoice : Document, Complaint : Document and so forth, each having specific property (and this fields loaded on the form), how do I write the action in the controller?
I thought to use something like (I'll omitt all the conversions, validations, etc, for brevity)
[HttpPost]
public ActionResult Save(dynamic doc)
{
int docType = doc.type;
switch (docType)
{
case 1:
var invoice = new Invoice(doc);
invoice.amount = Request.Form["amount_field"];
invoice.code = Request.Form["code_field"];
//and so forth for every specific property of Invoice
Repository.Save(invoice);
break;
case 2:
var contract = new Contract(doc);
contract.fromDate = Request.Form["fromDate_field"];
contract.toDate = Request.Form["toDate_field"];
//and so forth for every specific property of Contract
Repository.Save(contract);
break;
..... // and so forth for any document types
default:
break;
}
}
But it seems a very dirty approach to me. Do you have a better idea on how to achive this? Maybe there's a pattern that I don't know nothing about to approach this kind of scenario.
Update
A second idea comes to my mind. After commenting Rob Kent's answer, I thought I could take a different approach, having just one class Document with a property like
public IEnumerable<Field> Tipologie { get; set; }
where
public class Field
{
public int IdField { get; set; }
public String Label { get; set; }
public String Value { get; set; }
public FieldType ValueType { get; set; }
public List<String> PossibleValues { get; set; } // needed for ENUMERATION type
}
public enum FieldType
{
STRING, INT, DECIMAL, DATE, ENUMERATION
}
Is this a better approach? In this case I can have just an action method like
[HttpPost]
public ActionResult Save(Document doc)
But shoud I create the fields in the view in order to make the MVC engine do the binding back to the model?
Given that the class inheriting from Document in the first approach will probably be generated at run-time, would you prefer this second approach?
To keep it all hard-typed on the server, you could use an abstract base type with a custom binder. See my answer here to see how this works: MVC generic ViewModel
The idea is that every time they load a new set of fields, you change the BindingType form variable to the instantiated type of the handler. The custom binder is responsible for creating the correct type on submission and you can then evaluate that in your action, eg:
if (model is Contract) ...
I'm not sure if you will be able to set up different actions each with a different signature, eg,:
public ActionResult Save(Contract contract) ...
public ActionResult Save(Invoice invoice) ...
Pretty sure that won't work because Mvc will have already decided which method to call, or maybe it will firstly see what type it gets back and then decides.
In my linked example, I am checking for overridden base members but if that is not an issue for you, you just need to create the correct type.

Why isn't custom validation working?

ASP.NET MVC3/Razor newbie question:
I am setting up a model with custom validation. While the properties that I decorate with things like [Required] and [RegularExpression(...)] are performing as expected, I'm finding that the custom validation is not working. I made my model implement IValidatableObject, and I can hit a breakpoint inside the Validate() method and watch the method doing a yield return new ValidationResult(...); - but the form nonetheless gets posted.
Is there some secret switch that I'm missing?
If you are talking about server side validation, do you have the ModelState.Isvalid check?
http://weblogs.asp.net/scottgu/archive/2010/01/15/asp-net-mvc-2-model-validation.aspx
The form will be posted when you use IValidatableObject to validate model properties. As Joeri Jans says, you can still prevent this and return the page to the user during your action method:
public ActionResult MyAction(MyModel model)
{
if (ModelState.IsValid)
{
// code to perform action when input is valid
return [return something]
}
return View(model); // re-display form because ModelState.IsValid == false
}
If you want your custom validation to prevent the form from being posted, you need to validate on the client. The easiest way to do this is with the RemoteAttribute.
public class MyModel
{
[Remote("MyValidateAction", "MyController", HttpMethod = "POST")]
public string MyProperty { get; set; }
}
You can still keep your code in IValidatableObject, and validate it from an action method like so:
[HttpPost]
public virtual JsonResult MyValidateAction(string myProperty)
{
var model = new MyModel{ MyProperty = myProperty, };
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model,
new ValidationContext(model, null, null), results, true);
return isValid
? Json(true)
: Json(results[0].ErrorMessage);
}
The above action method does virtually the same thing as the default model binder. It constructs an instance of your viewmodel, then validates it. All validation rules will be checked, including your IValidatableObject code. If you need to send more properties to the action method for the construction of your viewmodel, you can do so with the AdditionalFields property of the RemoteAttribute.
Hope this helps.

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
}

html.TextBoxFor and html.Textbox, POSTing values, model in parameters

Alright guys, Need some help!
Im working with asp.net mvc3 razor (and am fairly new to it but did lots of web forms)
Okay so onto the problem
My question revolves around submitting a view.
I have a very complicated model that my view is based off (strongly typed).
I want to return the model into the arguments in the HttpPost method of the controller. do basically:
public ActionResult Personal()
{
DataModel dataModel = new DataModel();
FormModel model = new FormModel();
model.candidateModel = dataModel.candidateModel;
model.lookupModel = new LookupModel();
return View(model);
}
[HttpPost]
public ActionResult Personal(FormModel formModel)
{
if (ModelState.IsValid)
{
//stuff
}
return View(formModel);
}
Now...
I'm having trouble getting values into the formModel parameter on the post method.
This works (meaning i can see the value)but is tedious as i have to write exactly where it sits in a string every single field:
#Html.TextBox("formModel.candidateModel.tblApplicant.FirstName", Model.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="formModel.candidateModel.tblApplicant.FirstName" id="formModel_candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
This doesn't work:
#Html.TextBoxFor(c => c.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="candidateModel.tblApplicant.FirstName" id="candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
Now I'm assuming the problem lies in the discrepancy of the id's
So please answer me this:
Am i going about this the right way
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
Additionally, it seems that textboxfor is restrictive, in the manner that if you have a date time, how do you use the .toshortdate() method? This makes me think textboxfor isn't useful for me.
Quick clarification:
when i say textboxfor isn't working, it IS getting values when i GET the form. So they fill, but on the POST / submission, i can't see them in the formModel in the parameters.
Another side note:
None of the html helpers work, this is the problem. They aren't appearing in modelstate either.
Thanks everyone for the help
Answer:
html.TextBoxFor and html.Textbox, POSTing values, model in parameters
It was a problem in my view somewhere, i replaced all the code with the snippet in this answer and it worked.
Thank you again
Am i going about this the right way
Yes.
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
There is something else in your code that makes this not work. It's difficult to say since you haven't shown all your code. Here's a full working example which illustrates and proves that there's something else going on with your code:
Model:
public class FormModel
{
public CandidateModel candidateModel { get; set; }
}
public class CandidateModel
{
public Applicant tblApplicant { get; set; }
}
public class Applicant
{
public string FirstName { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new FormModel
{
candidateModel = new CandidateModel
{
tblApplicant = new Applicant
{
FirstName = "fn"
}
}
});
}
[HttpPost]
public ActionResult Index(FormModel formModel)
{
// the username will be correctly bound here
return View(formModel);
}
}
View:
#model FormModel
#using (Html.BeginForm())
{
#Html.EditorFor(c => c.candidateModel.tblApplicant.FirstName)
<button type="submit">OK</button>
}
Additionally, it seems that textboxfor is restrictive, in the manner
that if you have a date time, how do you use the .toshortdate()
method? This makes me think textboxfor isn't useful for me.
I agree that TextBoxFor is restrictive. That's why I would recommend you always using EditorFor instead of TextBoxFor. It will allow you to simply decorate your view model property with the [DisplayFormat] attribute and voilà. You get any format you like.
For example:
public class MyViewModel
{
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime CreatedAt { get; set; }
}
and in the view:
#model MyViewModel
#Html.EditorFor(x => x.CreatedAt)
will format the date exactly as you expect.
the model binder uses the name to bind the values to the model, and the html helpers e.g. Html.TextBoxFor uses the body of the lambda expression to set the name, however you can specify the name yourself which you are doing by using the Html.TextBox( helper
#Html.TextBoxFor(x=>x.candidateModel.tblApplicant.FirstName),
new{#Name="formModel.candidateModel.tblApplicant.FirstName"})
If your view is strongly typed, try the helper bellow, instead call each helper on each property
#Html.EditorForModel()
#Html.EditorFor(m => m.candidateModel)
#Html.EditorFor(m => m.lookupModel)
Update:
Well, have tried to use viewmodel to simplify this task? And when you get back the data you can map your real models. keep your views clean will give you less headaches in the future. Additionally you could use AutoMapper to help you.
Here a example if you think that will help you.
http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx

Resources