I have an MVC2 Application that uses MVVM pattern. I am trying use Data Annotations to validate form input.
In my ThingsController I have two methods:
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Details(ThingsViewModel tvm)
{
if (!ModelState.IsValid) return View(tvm);
try
{
Query q = new Query(tvm.Query);
ThingRepository repository = new ThingRepository(q);
tvm.Things = repository.All();
return View(tvm);
}
catch (Exception)
{
return View();
}
}
My Details.aspx view is strongly typed to the ThingsViewModel:
<%# Page Title=""
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Config.Web.Models.ThingsViewModel>" %>
The ViewModel is a class consisting of a IList of returned Thing objects and the Query string (which is submitted on the form) and has the Required data annotation:
public class ThingsViewModel
{
public IList<Thing> Things{ get; set; }
[Required(ErrorMessage="You must enter a query")]
public string Query { get; set; }
}
When I run this, and click the submit button on the form without entering a value I get a YSOD with the following error:
The model item passed into the dictionary is of type
'Config.Web.Models.ThingsViewModel', but this dictionary
requires a model item of type
System.Collections.Generic.IEnumerable`1[Config.Domain.Entities.Thing]'.
How can I get Data Annotations to work with a ViewModel? I cannot see what I'm missing or where I'm going wrong - the VM was working just fine before I started mucking around with validation.
I don't think the problem is with the validation.
Change this line;
tvm.Things = repository.All(); //Is this the Linq extension method 'All()'?
to this
tvm.Things = repository.ToList();
I don't know what this is or what it does;
new ThingRepository(q);
It takes a string parameter and returns some kind of Linq IQueriable or List? If that's returning something else it could be causing the problem.
Do you have client-side validation enabled? It might even be a quick hacky-fix, but regarding the error message - it's tough to say without extra info. Could you post your View and the rendered Html?
What does your route for Details look like?
If you set a breakpoint at the start of the Details method, does it get hit when you click on the submit button?
It looks like you could just declare your ThingsViewModel like so:
public class ThingsViewModel: IEnumerable<Thing>
and then implement the interface as appropriate to access the Things list.
I think that ASP.NET MVC might be trying to map your view to the wrong controller. When you return the view you might need to specify the view file name you're trying to use.
return View("ViewName")
Related
I have a very large asp.NET MVC application and I'm looking for the most efficient way of accomplishing adding a help document to each view. So in essence,I have added a partial view inside of the layout; that gets called on every page, that invokes a helper method to get return a string and the string is just the URL of the help docs for that specific page.
This is One approach:
{Key: "User_Management", Value: "/Account/User/List"},
{Key: "User_Management_Edit", Value: "/Account/User/Edit"}
The Key would be what i'm looking for as part of the URL and the value would be the returned URL string.
So if the URL is /User/Management/ Then the returned value would be the first of the list, /Account/User/List.
A second approach could be to store the keyUser_Management directly into the view as a ViewBag or ViewData and then check the value in the helper.
My only concern is that with approach One I would have to maintain the keys to make sure that they match the URL, and with approach Two, i'm wondering if it's inefficient to pass a string as a viewbag on each view.
Would love to hear some alternatives or if anyone has a better idea.
An alternative, good or bad...
What about a filter? You could create the below filter and explicitly set help document url on each action. The filter would take care of putting it in the same viewbag key so you didnt have to type it every time and risk typos.
public class HelpDocumentActionFilter : ActionFilterAttribute
{
string helpDocUrl { get; set; }
public HelpDocumentActionFilter(string _url)
{
helpDocUrl = _url;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//whatever viewbag key you wanted with an opportunity to hit the DB or transform the argument
filterContext.Controller.ViewBag.HelpUrl = helpDocUrl;
}
}
usage
[HelpDocumentActionFilter("/help/doc2")]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
Can I pass variable by reference in order to return value to caller. For example, can I use returnAmount in case below? I haven't seen such expression in samples I have analysed. What problems I will be faced while using it?
I understand that fact, that I can use custom class with required cont of fields in function return when I need to return more than one variable, but anyway I'm interested in fact is it possible to use out like in sample below.
public class MyController : Controller
{
public ActionResult balance( int amount, out int returnAmount )
{
returnAmount = 50;
return Proceed(a.Balance( amount));
}
}
I don't think you can do that. Instead create a ViewModel with that as a property. Update the viewmodel with whatever data you want. Then return the view and bind everything on the view through razor. If you don't want the page to completely reload, you'll have to use AJAX or a partial.
return View("viewname", viewmodel);
In MVC C# Web Applications with Razor, I constantly end up wanting to reuse View code for Create actions.
Imagine this scenario:
public class Taco
{
public Lunch Lunch { get; set; }
public Supper Supper { get; set; }
public string Cheese { get; set; }
}
public class Lunch
{
public IEnumerable<Taco> Taco { get; set; }
}
public class Supper
{
public IEnumerable<Taco> Taco { get; set; }
}
You have Lunch and Supper that have Tacos.
Now take these two use cases:
From Supper's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Supper Details with new Taco there
From Lunch's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Lunch Details with new Taco there
What is a scalable and MVC-Correct way to do this?
I have always felt like my process for this is hacked together and not very scalable. I might do something like:
Supper View:
#Url.Action("Create", "Taco", new { From = "Supper" })
Lunch View:
#Url.Action("Create", "Taco", new { From = "Lunch" })
Then take the "From" variable and pass it to
Taco Controller>Taco View Model>Taco View>Link Back To From
Is there a built in way to pass referrer information and is there a pre-defined design template for MVC to handle these situations?
Just literally reuse everything. For example, you can have just one action with one view and use the URL to determine behavior. All you need is a view model so the form can work with just one class type, and then you can map the data onto wherever it should go. For example:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{mealType:regex(supper|lunch)}/create")]
public ActionResult CreateMeal(string mealType, MealViewModel model)
{
if (ModelState.IsValid)
{
switch (mealType)
{
case "supper":
// map data to new `Supper` and save
break;
case "lunch":
// map data to new `Lunch` and save
break;
}
// do redirect
}
return View(model);
}
There's other ways to handle this without using Attribute Routing, but the general idea is that in some form or fashion you indicate which type of meal is being saved, and branch accordingly, creating and saving the appropriate class.
As far as I know there is no pre-defined template. But you can create a EditorTemplate if you want one and widely used in your razor views.
Also, instead of sending From in route parameters, you can have a property in Supper and Lunch classes like gobackUrl (just example) and generate gobackUrl in Create GET action and have it in hidden form value. So, the child action view will be more generic and you don't need to have if-else logic in parent view.
Here's the relevant part of my Index view (Index.cshtml):
#foreach (var item in Model) {
<li>
#Html.ActionLink(item.name, "Index", "Filler", new { cap = item }, null)
</li>
}
As you can see, the ActionLink is tied to the Index action on the Filler Controller, and is passing in the entire item (the model)- "item" is of type "capsule".
Now, on my Filler Controller, in the Index action:
public ActionResult Index(capsule cap)
{
var fillers = db.fillers.ToList();
return View(fillers);
}
The capsule class that was automatically generated by Entity Framework is:
namespace CapWorx.Models
{
using System;
using System.Collections.Generic;
public partial class capsule
{
public capsule()
{
this.fillers = new HashSet<filler>();
}
public int pk { get; set; }
public string name { get; set; }
public virtual ICollection<filler> fillers { get; set; }
}
}
The problem is "cap" is NULL in the above Index action. But, if I change the type to "object" instead of "capsule", I do get some weird non-null data, but I can't cast the object to "capsule". Does anyone know why this is NULL?
Thanks,
Mike
You usually just have to pass in the id to the action. For example, can you refactor your code so that it can take in a capsuleId, get the capsule from db and do whatever processing is needed. Adding the entire object to route values in ActionLink doesn't make any sense. Have a look at the link being generated. It is probably just something like ...?cap=Namespace.Capsule as the object would have be ToStringed
The first problem is in MVC you can't bind to an interface (ICollection). You'll need to change it to a List - List<filler>. The second problem you will face is that Lists/Arrays need to be represented in array notation for proper posting, something like name="books[0].book_id". Even though MVC does a lot of magic, the model in your link still has to be represented as a query string eventually.
Depending on what you are trying to do, you may be better off representing your model as a JSON object and posting with .ajax().
See this SO post for other ideas - Need help with binding Set with Spring MVC form
I'm not totally sure why this would work(I think you're nulling out the html attributes), but try to remove the "null" part of the actionlink.
Or, the controller which created the models is wrong.
Again, don't kill me for this.
#Html.ActionLink just generates an anchor element (...), so it makes no sense to attempt to bind a complete object to the routeValues parameter. As #manojlds says, it make much more sense to just pass the relevent key value, since you'll be performing the lookup then anyway (remember, the web is "stateless").
I'm trying to use DataAnnotations to add validation to my models in asp.NET MVC 2 RC2, using TryUpdateModel
var user = UserManager.Find(id);
this.TryUpdateModel<IProvisioningObject>(user, form.ToValueProvider());
This updates the model, but the validation is never called. I tried using TryUpdateModel as well (which is the direct type of user), not using the form value provider, using ProvisioningObject directly (which has the validation metadata), to no avail.
Googling for examples only gives me ways to use DataAnnotations by binding through a parameter
public ActionResult Update(User user)
Which I dislike for update scenarios.
Any tips and/or solutions?
EDIT
My objects are auto-generated objects from a WCF service.
I made partials to be able to add DataAnnotations.
I call TryUpdateModel three times because it apparently doesn't support inheritance, which I think is also my problem with DataAnnotations. I specify the validation attributes for ProvisioningObject, and the binding doesn't look for inherited stuff like that.
[MetadataType(typeof(ProvisioningObjectMetadata))]
public partial class ProvisioningObject : IProvisioningObject
{
public string DisplayNameInvariant { get { return string.IsNullOrEmpty(this.DisplayName) ? this.Name : this.DisplayName; } }
}
[MetadataType(typeof(UserMetadata))]
public partial class User : IUser
{
}
public class ProvisioningObjectMetadata
{
[DisplayName("Country")]
public string CountryIsoCode { get; set; }
[Required(ErrorMessageResourceType = typeof(Properties.Validation), ErrorMessageResourceName = "DisplayNameIsRequired")]
[TempValidator]
public string DisplayName { get; set; }
}
public class UserMetadata
{
[DisplayName("Username")]
public string Name { get; set; }
}
// Controller action
public ActionResult Update(string id, FormCollection form)
{
var user = UserManager.Find(id);
this.TryUpdateModel<IUser>(user.User, form.ToValueProvider());
this.TryUpdateModel<IPerson>(user.User, form.ToValueProvider());
this.TryUpdateModel<IProvisioningObject>(user.User, form.ToValueProvider());
if (ModelState.IsValid) // always true
{
return Redirect;
}
else
{
return View();
}
}
If I add the metadata for DisplayName in UserMetadata, it works as expected, but that seems very redundant for nothing. And it would mean I would also have to copy/paste all my inherited interfaces so TryUpdateModel behaves appropriately.
I guess I'm looking for a way that doesn't require me to copy and paste my validation attributes to inherited classes.
New Answer:
"My objects are auto-generated objects from a WCF service."
Autogenerated objects won't have any attributes on them. Are you defining your objects and their attributes on the server side or on the client side?
Old Answer:
If your metadata is not on IProvisioningObject then no validation will be called. The MVC2 default model binder only knows how to find "extra" [MetadataType(buddyClass)] validation information.
For update scenarios bind against DTOs and then map the DTOs, if IsValid() to your main model classes.
Implement IDataErrorInfo interface in your partial class
You will have to write custom validation for each field(where you can use data annotation class to validate each required property)
If you need code example then let me know. I will write it for you!
source: http://www.asp.net/(S(pdfrohu0ajmwt445fanvj2r3))/learn/mvc/tutorial-37-cs.aspx
How do you know that the validation is not being called? Are you checking ModelState.IsValid in your update controller and finding that it is erroneously coming back true?
A typical update pattern is:
UpdateModel(model);
if(!ModelState.IsValid) return View(model);
return RedirectToAction("Index");
If you are expecting some "IsValid" on your model to automatically be called, that will not happen. The data annotations work behind the scenes with the ModelState dictionary on the Controller base class.