Model binding validation errors - asp.net-mvc-4

In my custom model validation, I have the following:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext){
var repository = DependencyResolver.Current.GetService(typeof(IContactRepository));
IContactRepository repo = repository as IContactRepository;
USRContact c = repo.GetContactByID(Convert.ToInt64(bindingContext.ValueProvider.GetValue("ContactID").AttemptedValue));
c.FormalName = bindingContext.ValueProvider.GetValue("FormalName").AttemptedValue;
if (!repo.IsValidFormalName(c.ContactID, c.FormalName))
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
bindingContext.ModelState.AddModelError("FormalName", Resources.ErrorMsgs.FormalNameNotUnique);
return bindingContext.Model;
}
c.PreferredName = bindingContext.ValueProvider.GetValue("PreferredName").AttemptedValue;
c.Alias = bindingContext.ValueProvider.GetValue("Alias").AttemptedValue;
c.Pseudonym = bindingContext.ValueProvider.GetValue("Pseudonym").AttemptedValue;
c.GenderID = Convert.ToInt32(bindingContext.ValueProvider.GetValue("GenderID").AttemptedValue);
c.NationalityID = Convert.ToInt32(bindingContext.ValueProvider.GetValue("NationalityID").AttemptedValue);
c.ModifiedByID = Utilities.SessionUtil.Current.UserID;
c.ModifiedDate = DateTime.Now;
}
My controller calls this model binder by doing the following:
public ActionResult Update([ModelBinder(typeof(ModelBinder.ContactModelBinder))] USR.USRContact contact)
{
if (ModelState.IsValid)
{
repository.Update();
return View("~/Views/Shared/Contacts/ShowContactInfo.cshtml", repository.GetContactByID(contact.ContactID));
}
}
}
My viewmodel contains the data annotations to say that formal name is required and that alias needs to be less than 60 characters. How do I display the errors if the model binder transformed it to the persistent data model(USRContact) and my view is expecting the viewmodel?
Is there any way to make sure that on validation errors on the view model, the controller doesn't transform to persistent data model? Even if we do check for all the model errors in the data object and find the validation errors, how do we send the user back to the view they were just in with the errors next to the textboxes that they had the error in.
Thanks for the help!
Safris

I think the issue you may be facing is that once you push those values into the other object through the custom binder they are no longer the same as they were on the page.
A property called "PropertyValue" with an Html.ValidationFor(x=>x.PropertyValue) is going to look in the ModelState error collection for items with PropertyValue.
Once you have pushed those into Contact now the value is Contact.PropertyValue. If you validated it then it will be added to the ModelState as "Contact.PropertyValue" This will only be picked up by Html.ValidationFor(x=>x.Contact.PropertyValue)
The easiest solution is to make sure that your input and output's follow the same structure. If you can render the items as Html.TextBoxFor(x=>x.Contact.SomeProperty) then things will be fine.

Related

Why is my record being deleted from the db when I attempt to update the record from entity framework MVC?

When I attempt to update a record from entity framework the record is being deleted from the table. There are no errors thrown so it really has me baffled what is happening.
I am fairly new to entity framework and asp.net. I've been learning it for about a month now.
I can update the record without any issues from SQL Server but not from vs. Here is the code to update the db:
// GET: /Scorecard/Edit/5
public ActionResult Edit(int id, string EmployeeName)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CRS_Monthly crs_monthly = GetAgentById(id);
crs_monthly.EmployeeName = EmployeeName;
if (crs_monthly == null)
{
return HttpNotFound();
}
return View(crs_monthly);
}
// POST: /Scorecard/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="REC_ID,Cur_Plan,Plan_Update,Comments,Areas_Improve,Strengths,UPDATED_BY,UPDATED_TIME,Agent_Recognition")] CRS_Monthly crs_monthly)
{
if (ModelState.IsValid)
{
crs_monthly.UPDATED_TIME = DateTime.Now;
crs_monthly.UPDATED_BY = Request.LogonUserIdentity.Name.Split('\\')[1];
db.Entry(crs_monthly).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(crs_monthly);
}
When I run the debugger crs_monthly is valid and looks fine until db.SaveChanges(). Any help is greatly appreciated!
You should never save an instance of your entity created from a post, especially when you're utilizing Bind to restrict which properties are bound from the post data. Instead, always pull the entity fresh from the database and map the posted values on to it. This ensures that no data is lost.
Using Bind is a horrible practice, anyways. The chief problem with it is that all your properties are listed as string values, and you're introducing maintenance concerns. If remove one of these properties or change the name, the Bind list is not automatically updated. You must remember to change every single instance. Worse, if you add properties, you have to remember to go back and include them in this list or else your data just gets silently dropped with no notice.
If you need to only work with a subset of properties on your entity, create a view model containing just those properties. Then, again, map the posted values from your view model onto an instance of your entity pulled fresh from the database.

Switching MVC view on Post back using strongly typed views/view models

User requests page for Step1, fills out and submits form that contains selected person, so far so good. After validation of ModelState the next viewmodel is constructed properly using the selected person. I then attempt a redirect to action using the newVM but find on entry to Step2 that MVC wipes out the viewmodel attempted to be passed in. I suspect this is due to how MVC attempts to new up and instance based on query string results. I'll put a breakpoint in and check that, but am wondering how does one change a view from a post back with a new view model passed in?
public ActionResult Step1()
{
var vm = new VMStep1();
return View(vm);
}
[HttpPost]
public ActionResult Step1(VMStep1 vm)
{
if (ModelState.IsValid)
{
var newVM = new VMStep2(vm.SelectedPerson);
return RedirectToAction("Step2", newVM);
}
return View(vm);
}
public ActionResult Step2(VMStep2 vm)
{
return View(vm);
}
I can fix this by containing VMStep2 and a partial to Step2 in Step1 view, but that requires hide and seek logic when really I just want user to see Step2.
I don't see why you should want to call RedirectToAction! What it does it the following:
it tells your browser to redirect and like it or not your browser doesn't understand how to handle your object -- what it does understand is JSON. So if you really insist on using return RedirectToAction("Step2", newVM); you should consider a way to serialize your VMStep2 object to JSON and when the browser requests the Redirect, it will be properly passed and created in your action method public ActionResult Step2(VMStep2 vm)
HOWEVER I'd use a much simpler way ---
instead of
return RedirectToAction("Step2", newVM);
I would use
return View("Step2", newVM);
Thanks to everyone for the great input!
Here's what I did...
I created three views MainView, Step1View, Step2View (Step 1 and 2 were partial strong typed views)
I created a MainViewModel that contained VMStep1 and VMStep2
When controller served Step1 the MainViewModel only initialized VMStep1 and set state logic to tell MainView Step1 was to be shown.
When user posted back the MainView containing the MainViewModel, the MainViewModel knew what to do by the answers provided in VMStep1.
VMStep2 was initialized on the post back, and state was set to tell MainView to show Step2. VMStep1 was no longer relevant and was set to null.
User was now able to answer using VMStep2 and all was well.
The key to this working is that some flag tells the view which partial to show, the partial takes a model supporting it's strong type which is initialized at the right time. End result is fast rendering and good state machine progression.

Data is not inserting to the database

I am new to asp .net MVC 4.
I have one text box and the text box value I am fetching from one table.But while clicking on submit button this value I want to insert into different table , which is not inserting and showing error.It is taking value as null.
coding
View
#Html.TextBox("empname", (string)ViewBag.empname, new { #readonly = "readonly" })
controller
[HttpGet]
public ActionResult Facilities()
{
mstEmpDetail emp = new mstEmpDetail();
emp = db.mstEmpDetails.Single(x => x.intEmpId == 10001);
ViewBag.empname = emp.txtEmpFirstName;
return View();
}
[HttpPost]
public ActionResult Facilities(TrnBusinessCardDetail bc)
{
var empname1 = ViewBag.empname;
bc.txtfirstName = empname1;
db.TrnBusinessCardDetails.Add(bc);
db.SaveChanges();
return RedirectToAction("Facilities");
}
While I was working with normal text box it was inserting properly,but when I have retrieve
fro DB then i am getting this problem ?
How to solve this problem ?
Viewbag is a one way street - you can use it to pass information to the view, but you cannot use it to get the information from the view. The statement ViewBag.empname in your POST method has a value of null in your code.
As suggested by #dotnetom, ViewBag is a one way street. MVC is stateless so a POST request is not a "Round Trip" from previous get request. Thus your ViewBag can not hold its state.
MVC can determine (and construct) your action parameters from Form Parameters. In your case you have added a textbox with name "empname". So you should get this value as parameter in your POST request.
[HttpPost]
public ActionResult Facilities(TrnBusinessCardDetail bc, string empname)
{
bc.txtfirstName = empname;
db.TrnBusinessCardDetails.Add(bc);
db.SaveChanges();
return RedirectToAction("Facilities");
}
This would be simplest of solution given your problem. More appropriate would be binding your textbox directly with you model property. This way you will not have to worry about retrieving and assigning property value to model in your controller.
I think the problem is when you are using var empname1 = ViewBag.empname; in post controller because ViewBag.empname lost its value at that time.

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[X]', but this dictionary requires a model item of type 'X'

*CORRECTION
The problem occurs when my view is called to populate a list from my user table.
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Mike.Models.User]', but this dictionary requires a model item of type 'Mike.Models.User'.
Here is my controller action:
public ActionResult Registration(Mike.Models.User user)
{
if (ModelState.IsValid)
{
using (var db = new UserContext())
{
var crypto = new SimpleCrypto.PBKDF2();
var encrypPass = crypto.Compute(user.password);
var sysUser = db.Users.Create();
sysUser.LastName = user.LastName;
sysUser.FirstName = user.FirstName;
sysUser.Email = user.Email;
sysUser.password = encrypPass;
sysUser.passwordSalt = crypto.Salt;
sysUser.UserID = user.UserID;
db.Users.Add(sysUser);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
return View(user);
}
Can someone please help me.... There are responses to similar questions on the internet but I believe mine is unique.. I have searched for weeks to no avail.
Thanks in advance,
Renior
Here is my simple controller action...
public ActionResult Index()
{
return View(db.Users.ToList());
}
and my razor syntax.
#model IEnumerable
Im trying to populate a view of my user table list..
In your Registration view at the top where your model declaration is, instead of this:
#model List<Mike.Models.User>
you need to have:
#model Mike.Models.User
You probably used strongly typed scaffolding feature to generate your view but instead of details option you chose a list option...
Take this at face value - yours is not unique. Your problem is you are passing an array of user to a controller action that expects a user.
You need to post your HTML but it is probably something like #model List user or something instead of a single user.
If your model represents a single user then pass that to the controller. If opposite, do opposite,
If you want to pass a list to the controller use list users
edit
make your razor syntax
#model Mike.Models.User

HTTP GET to return custom model with data from external database with Umbraco MVC Surface Controller

I am currently working on an Umbraco MVC 4 project version 6.0.5. The project currently uses Vega.USiteBuilder to build the appropriate document types in the backoffice based on strongly typed classes with mapping attributes. Consequently, all my razor files inherit from UmbracoTemplatePageBase
I am coming across a road block trying to invoke a HTTP GET from a razor file. For example a search form with multiple fields to submit to a controller action method, using a SurfaceController using Html.BeginUmbracoForm.
My Html.BeginUmbracoForm looks like this
#using (Html.BeginUmbracoForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I basically have a scenario where I will like to retrieve some records from an external database outside of Umbraco (external to Umbraco Database) and return the results in a custom view model back to my Umbraco front end view. Once my controller and action method is setup to inherit from SurfaceController and thereafter compiling it and submitting the search, I get a 404 resource cannot be found where the requested url specified: /umbraco.RenderMVC.
Here is my code snippet:
public ActionResult FindTyres(string maker, string years, string models, string vehicles)
{
var tyreBdl = new Wheels.BDL.TyreBDL();
List<Tyre> tyres = tyreBdl.GetAllTyres();
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Version, vehicles, StringComparison.OrdinalIgnoreCase)).ToList();
var tyreSearchViewModel = new TyreSearchViewModel
{
Tyres = tyres
};
ViewBag.TyreSearchViewModel = tyreSearchViewModel;
return CurrentUmbracoPage();
}
I then resort to using standard MVC, Html.BeginForm (the only difference). Repeating the steps above and submitting the search, I get the following YSOD error.
Can only use UmbracoPageResult in the context of an Http POST when
using a SurfaceController form
Below is a snippet of the HTML BeginForm
#using (Html.BeginForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I feel like I am fighting the Umbraco routes to get my controller to return a custom model back to the razor file. I have googled alot trying to figure out how to do a basic search to return a custom model back to my Umbraco front end view till the extent that I tried to create a custom route but that too did not work for me.
Does my controller need to inherit from a special umbraco controller class to return the custom model back? I will basically like to invoke a HTTP GET request (which is a must) so that my criteria search fields are reflected properly in the query strings of the url. For example upon hitting the search button, I must see the example url in my address browser bar
http://[domainname]/selecttyres.aspx/TyresSearch/FindTyresMake=ASIA&Years=1994&Models=ROCSTA&Vehicles=261
Therefore, I cannot use Surface Controller as that will operate in the context of a HTTP Post.
Are there good resource materials that I can read up more on umbraco controllers, routes and pipeline.
I hope this scenario makes sense to you. If you have any questions, please let me know. I will need to understand this concept to continue on from here with my project and I do have a deadline.
There are a lot of questions about this and the best place to look for an authoritative approach is the Umbraco MVC documentation.
However, yes you will find, if you use Html.BeginUmbracoForm(...) you will be forced into a HttpPost action. With this kind of functionality (a search form), I usually build the form manually with a GET method and have it submit a querystring to a specific node URL.
<form action="#Model.Content.Url"> ... </form>
On that page I include an #Html.Action("SearchResults", "TyresSearch") which itself has a model that maps to the keys in the querystring:
[ChildAction]
public ActionResult(TyreSearchModel model){
// Find results
TyreSearchResultModel results = new Wheels.BDL.TyreBDL().GetAllTyres();
// Filter results based on submitted model
...
// Return results
return results;
}
The results view just need to have a model of TyreSearchResultModel (or whatever you choose).
This approach bypasses the need for Umbraco's Controller implementation and very straightforward.
I have managed to find my solution through route hijacking which enabled me to return a custom view model back to my view and work with HTTP GET. It worked well for me.
Digby, your solution looks plausible but I have not attempted at it. If I do have a widget sitting on my page, I will definitely attempt to use your approach.
Here are the details. I basically override the Umbraco default MVC routing by creating a controller that derived from RenderMvcController. In a nutshell, you implement route hijacking by implementing a controller that derives from RenderMvcController and renaming your controllername after your given documenttype name. Recommend the read right out of the Umbraco reference (http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers) This is also a great article (http://www.ben-morris.com/using-umbraco-6-to-create-an-asp-net-mvc-4-web-applicatio)
Here is my snippet of my code:
public class ProductTyreSelectorController : Umbraco.Web.Mvc.RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
var productTyreSelectorViewModel = new ProductTyreSelectorViewModel(model);
var maker = Request.QueryString["Make"];
var years = Request.QueryString["Years"];
var models = Request.QueryString["Models"];
var autoIdStr = Request.QueryString["Vehicles"];
var width = Request.QueryString["Widths"];
var aspectRatio = Request.QueryString["AspectRatio"];
var rims = Request.QueryString["Rims"];
var tyrePlusBdl = new TPWheelBDL.TyrePlusBDL();
List<Tyre> tyres = tyrePlusBdl.GetAllTyres();
if (Request.QueryString.Count == 0)
{
return CurrentTemplate(productTyreSelectorViewModel);
}
if (!string.IsNullOrEmpty(maker) && !string.IsNullOrEmpty(years) && !string.IsNullOrEmpty(models) &&
!string.IsNullOrEmpty(autoIdStr))
{
int autoId;
int.TryParse(autoIdStr, out autoId);
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase) &&
t.AutoID == autoId)
.ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
else if (!string.IsNullOrEmpty(width) && !string.IsNullOrEmpty(aspectRatio) && !string.IsNullOrEmpty(rims))
{
tyres = tyres.Where(t => string.Equals(t.Aspect, aspectRatio, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Rim, rims, StringComparison.OrdinalIgnoreCase)).ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
var template = ControllerContext.RouteData.Values["action"].ToString();
//return an empty content result if the template doesn't physically
//exist on the file system
if (!EnsurePhsyicalViewExists(template))
{
return Content("Could not find physical view template.");
}
return CurrentTemplate(productTyreSelectorViewModel);
}
}
Note my ProductTyreSelectorViewModel must inherit from RenderModel for this to work and my document type is called ProductTyreSelector. This way when my model is returned with the action result CurrentTemplate, the Umbraco context of the page is retained and my page is rendered appropriately again. This way, all my query strings will show all my search/filter fields which is what I want.
Here is my snippet of the ProductTyreSelectorViewModel class:
public class ProductTyreSelectorViewModel : RenderModel
{
public ProductTyreSelectorViewModel(RenderModel model)
: base(model.Content, model.CurrentCulture)
{
Tyres = new List<Tyre>();
}
public ProductTyreSelectorViewModel(IPublishedContent content, CultureInfo culture)
: base(content, culture)
{
}
public ProductTyreSelectorViewModel(IPublishedContent content)
: base(content)
{
}
public IList<Tyre> Tyres { get; set; }
}
This approach will work well perhaps with one to two HTTP GET forms on a given page. If there are multiple forms within in a page, then a good solution will may be to use ChildAction approach. Something I will experiment with further.
Hope this helps!