I have a service which has a method that's called when a certain controller method is triggered.
My service returns a custom result object PlacementResult in which I want to communicate errors that may have happened (validation) back to the controller method.
Should PlacementResult have a ModelState or a ModelStateDictionary to communicate errors back to the controller (and finally view)? How would I string this together?
Finally, how do I get the ModelState/ModelStateDictionary (whichever you tell me I should choose) back into the view (highlighting the appropriate text box, show the error message etc.)?
Thank you !
This is a good link that shows how a service can perform validation and communicate the result back to the controller:
http://www.asp.net/mvc/tutorials/validating-with-a-service-layer-cs (fixed link)
No, you do not want to add a ModelStateDictionary to your result type. There is already a ModelStateDictionary on the Controller (in the ModelState property). It is not appropriate for results to set the controller's model state. That should be done during binding or within the controller action itself. Use a custom model binder if you need to.
Your choose one can see the model state errors by examining the controller's ViewData.ModelState property.
Your PlacementResult should return a dictionary object or a List which you should merge with the model state at the beginning of each action.
If you step through you will notice the controllers model state dictionary contains all your input fields, their values and the errors associated with them. You want to merge the PlacementResult errors into the model state dictionary at the appropriate keys. This is how the view engine knows which fields to flag as invalid.
ModelState.Merge(PlacementResult);
if(ModelState.IsValid)
{
...
}
I don't know what your PlacementResult looks like, see if you can possibly utilize this in your view:
ModelState.AddModelError(ErroredProperty, ErrorMessage);
Make sure you return the object that failed back to the view
return View(myObjectInstance);
Based on SoC I think you have to return errors from your services and merge them in your ModelState if needed.
But our objective is maintain decoupling and also to use ModelState.Merge() method. it isn't?
There is an concrete implementation that could help
You can pass the Controller to your method, since the Controller class contains the ModelState property. Once in your method you can do the following:
private PlacementResult BuildResult(Controller controller)
{
controller.ModelState.AddModelError(propertyName, errorMessage);
}
In your action...
BuildResult(this);
if(ModelState.IsValid) {...
Related
I'm using C# and MVC 5, but for some reason my view is passing an anonymous type back to the controller, and it's not happy about that. The specific error is:
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Core.dll but was not handled in user code
Additional information: 'object' does not contain a definition for 'Id'
This is the specific controller function signature:
[HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
public ActionResult TaskEdit(dynamic model, bool continueEditing)
The error occurs when I try to reference model.Id in that function. The beginning of the view is this:
#model ProjectModelShopModel
// ...
#using (Html.BeginForm(null, null, FormMethod.Post, Model))
{
// code
}
How do I solve this error? I can provide more code if necessary.
EDIT: I use a dynamic type in TaskEdit because three views call that function, each with a different model. The function is nearly identical in each. I don't use inheritance because I screwed up too much early on and it would take way too much work to fix now.
This post confirms (without source) my guess that the default modelbinder can't really work with dynamic parameters.
The default modelbinder looks for existing properties on the parameter types (of which dynamic has very little), and then tries to map the posted fields to those properties.
Workarounds are to use public ActionResult TaskEdit(FormCollection formCollection) and fill your model in your controller, or to use a custom modelbinder.
It is model binding problem. You are sending a post request to a single method with 3 types of models. That method has to accept that model and expose you the values of that. There are couple of ways to achieve this -
Create your own model binder
[HttpPost]
[ModelBinder(typeof(CustomModelBinder))]
CustomModelBinder is an extension of default model binder. You can find the implementation here
Use Form Collection instead of binding to specific model to get data
[HttpPost]
public ActionResult ActionName(FormCollection formData)
{
var variablename = Request.Form["VariableName"];
}
You can find the example here
Edit Modified example link for formcollection
I have a simple form that posts a ViewModel to an Action method. Before saving the information the ModelState is checked with a standard if(ModelState.IsValid). Then a new object is created and saved. Great, it works.
Recently another dev went in and created a new view with my original ViewModel. He also added a new [Required] property to the ViewModel to make his logic work.
By doing that his logic broke my initial logic. Because my initial view doesn't use his new Required property so ModelState.IsValid check now fails and my code doesn't run.
What is the best approach to take here ? Though I don't want to but should I get rid of ModelState.IsValid check on my Post actions or can I somehow flag his new property to Not be required when used in my original views or when being posted in my action method ?
Thank you in advance.
You can use attribute [Bind(Exclude="")] in your action method like below. Then, when you submit the form, model binder will ignore that property even it is required.
[HttpPost]
public ActionResult Index([Bind(Exclude = "AdditionalProperty")]YourModel model)
{
//
}
You can derive the model from IValidatableObject, and then perform your own custom validations using
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
}
How do I use IValidatableObject?
Edtied to add: If it were me, it would seem to make more sense to have him create an inherited model from your model, even if it only has 1 property in it. This would keep the native MVC validations working correctly with minimal effort on your part.
you have two choice(to the best of my knowledge!), first you can unbind the that required property while posting it to the action:
[HttpPost]
public ActionResult Create([Bind(Exclude = "RequiredProperty")]MyViewModel myViewModel)
{
if(ModelState.IsValid)
{
//
}
}
but you can solve this issue for your application by mapping the ViewModel to View in your get action, and send it to the view. try this great article
This is more of a high-level question than anything else.
I have a MVC project which, among other things, manages users.
I am currently sticking to a more strict approach that on my view pages I only use a model which is declared, meaning that I have a:
#model MVCApp.Models.SomeModel
At the top of each View.cshtml
There are some pages that require more than 1 model. At this point I consolidate the 2 models into 1, and send it to the view as one model.
Here comes the tricky part. Let's say I have some model which holds the user data. That user data is stored in the session cookies (typical Forms Authentication). It seems weird to me that I now have to wrap each and every model I use with my own model that holds both the User Model and the model which I want to use inside that View.
The question that I ask myself is why not pass the User Model to the ViewBag and use it inside the View. Why is that considered to be bad practice? It allows me to attach that model to every page without having to ultimately duplicate all my models.
I'd love to get some guidance. I might be looking at this the wrong way. Any help will be much obliged.
Thanks,
There are a couple of reasons why ViewBag should be avoided:
ViewBag is weakly typed
You don't get Intellisense in the view
Your view is a mixture of ViewBag and a view model and your view gets information from different places instead of centralizing everything into a strongly typed view model
ViewBag doesn't work with strongly typed helpers and expressions because dynamic types cannot be used with extension methods (that's not a limitation of ASP.NET MVC, it's .NET => you cannot dispatch extension methods on dynamic types)
Due to this weak typing nature of ViewBag you will have to cast in your views turning them into spaghetti code because HTML helpers expect specific types to be passed as arguments
... this list goes on (I really don't have time to fill it but it is very large list)
So now that we know that ViewBag is bad and shouldn't be used there are different ways you could solve this requirement by using view models.
One possibility is to use the Html.Action helper which allows you to insert some portion of HTML in the view by going through a full Controller/Model/View lifecycle. This way you could have a commonly used widget without interfering with your main view model.
Another possibility is to have a base view model which will contain a property representing user details and which will be populated by a custom global action filter that could executed everytime a controller action has finished executing and returned a view result. The action filter could intercept this view result, read the authentication cookie information and set the view model property. This assumes that all your view models derive from the common base view model. This obviously makes sense if you need to display this user information on each page.
For example:
public class UserInfoInjectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (result == null)
{
// the controller action didn't return any view result => no need to continue
return;
}
var model = result.Model as BaseViewModel;
if (model == null)
{
// the controller action didn't pass a model or the model passed to the view
// doesn't derive from the common base view model that will contain
// the user info property => no need to continbue any further
return;
}
model.UserInfo = ... go ahead and read the forms authentication cookie
userData portion and extract the information
you are looking for
}
}
Now all that's left is to register this action filter as a global action filter and it will be applied on all controller actions.
Now if all your view models derive from this BaseViewModel you will know that once you arrive in the view the UserInfo property will be populated with the relevant information without polluting all your controller actions with code that does the fetching of this property. And still you get strong typing in the view => no ViewBag (youpeeee).
Of course depending on your specific scenario there might be other ways to achieve that (obviously not involving any ViewBag).
You can an ActionFilterAttribute and in those Actions that load views which needs specific ViewBag items, you initialize them.
I don't recommend it, as it will be harder to mantains, but will be a generic approach which may solve your problems.
I have a controller with several action methods requiring the same list of data from a certain database. Since most of the actions were going to need access to the list, I quickly populated a private member variable with necessary list of data items directly in the constructor of my controller.
All was well and good until the database went down and an exception was thrown in the constructor. Apparently, that circumvents the normal HandleError functionality.
My goal is for this exception to be caught and the user redirected to an error view.
What is the proper way to load the data for all actions?
Is it appropriate to put a database call in OnActionExecuting?
Is there some way to do decorate the specific actions with an attribute that loads the data?
Am I over-thinking it? (After all, I could just drop a private method in the controller and call it from each action requiring the data)
You could create the private method and have it populate your list (if it's not already populated) and then return the list. This way your only calling the method to populate it when it's needed the first time, and you take fragile code out of your controller's constructor. It's going to much easier to handle the exception in your action methods than elsewhere.
Controllers (as objects) are being instantiated for every request. Therefore, there is no need to optimize data within controller which would be "reused" in many actions (as Jeff Reddy suggested). Unless you call an action method explicitly from another action method (which is bad practice anyway).
Make a private method GetData() that gets data from database and call it in every action.
However, you probably do want to avoid expensive database round-trips that get the same data over and over, then consider using HttpRuntime.Cache. You could save data there on the first call to GetData() and retrieve it from cache on subsequent requests.
If you need the model inside all your controller actions you could define a custom model binder for a given model and overriding the BidModel method which will query the database and populate this model. Then your controller actions could take this model as action argument:
public ActionResult Foo(MyModel model)
{
...
}
public ActionResult Bar(MyModel model)
{
...
}
If you don't need the model inside each action but inside each view you could externalize it as a widget using the Html.RenderAction helper.
We have a model (say, List<string>). The function that builds the list is non-deterministic and the output is needed to be referenced in both controller and the view during the lifetime of the request. Since it's per-request, it cannot be static or singleton.
It's a common structure and it can be referenced from any view or controller.
Since we can't access controller from the view (by principle, and we agree), we cannot keep it in the controller. We're currently keeping it in the ViewData dictionary and initialize it in the controller, or the view (if the controller didn't need it).
We think that using ViewData for this purpose may not be ideal since it's not created to be consumed by a controller in the first place. Is there a better way to share common per-request data between Controller and the View? If not we'll stick with ViewData.
There is HttpContext.Items dictionary but I'm not sure if it fits to this purpose.
the output is needed to be referenced in both controller and the view during the lifetime of the request
The way MVC works, the Action code in the Controller is executed, and the resulting data is passed to the view engine that draws the page using the info you passed either with the call to View(data) or in the ViewData dictionary.
I don't know what you are trying to do, but it sounds like it's more a problem of a bad approach than a technical one (I might be wrong, though).
Could you explain why you need the controller while the View is rendered? If you need any logic associated with the List (to process it or do anything with it) I would just create a new class that extends List<T>, add the logic to that class instead of the controller, and pass an object of that class to the View, either using View() or ViewData[].
What is exact thing you're trying to do?
Seems like you just asking about the way to pass some data from the Controller to the View which is rather trivial task. Just use ViewData, yes, or ViewBag in MVC3 case or use ViewModels.
Or there is somewhat special case? What does "referencing from Controller and from View" mean? Where the data is coming from? Usually the case is that Controller prepares data for the View and passes it as an ActionResult (or better, as a ViewModel). View should never take some data on its own bypassing the controller.
Controller action should always be called be first. If you have multiple controllers calling the same view/partial view then you should be refactoring the code to one method and call that.
ViewData is the solution to do this, if your really wanting "once access" type information then maybe TempData but ViewData is designed for this.