Handling partial updates (under-posting) with AutoMapper - c#

I'm trying to find a method of handling partial updates; A ViewModel has 5 fields, but only one field is sent in the Request (i.e. only send fields that were modified instead of posing the entire view model), since null values were not explicitly sent for the other fields, I don't think that they should be set to null. Since AutoMapper does not have any kind of support for a Bind list, it's proving difficult to find an elegant solution...
public virtual IActionResult Edit(int id, ViewModel1 viewModel)
{
var model = GetModel(id);
mapper.Map(viewModel, model);
// any field that was not posted, but exists in the ViewModel1 is now null in the model
...
}
The only approach I can think of, is to use Request.Form.Keys and Reflection to build an ExpandoObject that only contains the posted properties, then set CreateMissingTypeMaps and ValidateInlineMaps to allow AutoMapper to map the dynamic types... It just feels like this is a dirty workaround to compensate for functionality that's missing in AutoMapper... Is there a standard way of handling this?

Related

Returning multiples of the same model to view [duplicate]

I've been able to successfully return a model to a view and display the results in a strongly-typed fashion.
I have never seen an example where multiple models get returned. How do I go about that?
I suppose the controller would have something like this:
return View(lemondb.Messages.Where(p => p.user == tmp_username).ToList(), lemondb.Lemons.Where(p => p.acidity >= 2).ToList());
Does MVC let you return multiple models like that?
And then in the view I have this line at the top of the file:
#model IEnumerable<ElkDogTrader.Models.Message>
And I frequently make calls to "model" in the view.
#foreach (var item in Model)
If there were 2 models, how would I refer to them separately?
Is this possible with multiple models, or is this why people use ViewBag and ViewData?
You can create a custom model representing the data needed for your view.
public class UserView
{
public User User{get;set;}
public List<Messages> Messages{get;set;}
}
And then,
return View(new UserView(){ User = user, Messages = message});
In the view:
Model.User;
Model.Messages;
The ViewBag is useful because it is dynamically typed, so you can reference members in it directly without casting. You do, however, then lose static type checking at compile time.
ViewData can be useful if you have a one-off on your view data types and know the type and will be doing a cast in the view anyway. Some people like to keep the actual typed view pure in a sense that it represents the primary model only, others like to take advantage of the type checking at compile time and therefore make custom models needed for the view.
I believe ViewModel should be the way to go. Within the customary ViewModel, you can reference other models or define all the related domain models in the viewModel itself.

Call custom ModelBinder in .NET Core

I'm creating a custom ModelBinder in .NET Core 1.1.0, and I think I have it mostly figured out. I want to use this binder only when I specifically want to, but I can't figure out how to accomplish this. I want this model binder to be ignored when I don't call it, but when I call it I want all others to be ignored. How can I accomplish this?
The two things that seem feasible are the parameter attributes [Bind] and [ModelBinder], but neither of these really works.
I first tried the below:
[HttpGet]
public async Task<IActionResult> Get([Bind("test")] int userId)
{
// stuff
}
When my custom IModelBinderProvider is hit, the ModelBinderProviderContext contains a BindingInfo property, which in turn contains an IPropertyFilterProvider. When debugging, that filter provider contains a collection called Include with my value of test. There doesn't appear to be any way to check for that programmatically, however--there's no way that I can find to actually access that collection. It's null if nothing is set, so I could hypothetically check for null, but that's very messy and isn't a good idea.
To illustrate, here's the debugger info for the ModelBinderProviderContext:
Next, I tried using this code:
[HttpGet]
public async Task<IActionResult> Get(
[ModelBinder(BinderType = typeof(MyModelBinder))] int userId
)
{
// stuff
}
This attribute appears to have no effect whatsoever. It does not force MyModelBinder to be used; model binders are used in the order specified in Startup.cs (the list in MvcOptions).
The PropertyFilterProvider contains a BindAttribute instance which implements the IPropertyFilterProvider interface. You could cast the instance to a BindAttribute and access the Include property:
var bindAttribute = context.BindingInfo.PropertyFilterProvider as BindAttribute;
var include = bindAttribute?.Include;
Keep in mind that the cast may not succeed, resulting in bindingAttribute being null.

ViewBag as a holder of session models

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.

How can I collect model validation error messages?

I am building an ASP.NET MVC application, and I am trying to find a way where I can collect data from the user in a custom view model, try and set these values to one or more of my entities, then based on validation logic on those entities, collect error messages if any and get them back to the view. I am new to MVC and web design in general, it is therefore quite possible that I am making major conceptual errors, but I have tried to research as far as I could.
I realize that this is more work than having the view be strongly typed to the entity, where it would then be easy to have the validation errors display, as in this tutorial. However, I don't want to do this for security and because there are some places where I want to have values collected from a single view model to be set in multiple different entities.
I also realize that I could set validation rules on the view model itself, rather then on the entity, but this seems like poor architecture, as I would have to define them redundantly in different view models, and I would then be less sure whether I had prevented bad values from being persisted to the database.
My plan is therefore to have the validation rules be set on the entity itself and to have the view model as a dumb container. Then, in a different location in the application, I would apply the values from the view model to my entity(ies) in accordance my business logic. At this point, I would like my validation logic to be called. If the data is invalid, I plan on setting the error string in the custom attribute on the view model to the error from the validation logic on the entity. I am thinking it would go something like this:
public class CustomViewModel()
{
[SomeCustomValidation()] //Has a place for an error string and a boolean IsValid
String Property { get; set; }
}
public class BusinessLogic()
{
CustomViewModel TestForValidity(CustomViewModel viewModel)
{
MyEntity.Property = viewModel.Property;
// if(MyEntity.IsValid)? catch SomeSortOfException?
// collect error message, put it in the attribute on the view model, set IsValid to false
}
}
public class MyEntity()
{
[MoreCustomValidation()]
public String Property { get; set; }
}
I therefore have three questions:
When I try to pass data that does not satisfy my validation rules, will some sort of error or exception be thrown? Is there some indication I can use or collect when I try to pass in invalid data?
If there is some error or exception thrown, how can I collect the error message so I can assign it to my view model?
Most importantly, am I going about this all wrong? Can attributes not be modified at runtime, for example to include a new error message or to change IsValid to false? I know that I can use reflection to access the attributes. If I can modify them, how would I do so?
Thank you in advance for your help. I apologize if I misunderstand something big.
It seems you might be over-complicating things a bit. I think what you want to do is prevent the model binder from binding to properties that it should not be able to, as well as retaining the ability to check ModelState.IsValid when properties on your model do not meet the requirements of their validation attributes.
IMO the best way to accomplish this is through the use of what I call "strongly-typed binding filters". First define an interface with only the properties that you want the model binder to be allowed to bind on your model.
public interface INewBlogPost
{
string Title { get; set; }
string Body { get; set; }
}
Then ensure your entity inherits from the binding filter interface.
public class BlogPost : INewBlogPost
{
...
}
Next, modify your action method to create a new entity and manually invoke the model binder whilst typing it to the interface you just defined.
public ActionMethod NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid)
{
...
}
}
Because you passed in a type when invoking the model binder via TryUpdateModel you explicitly told the model binder what type to bind to. This means that the model binder will only have access to the properties listed in the interface. Now when you pass a model into the method to be bound it will have to be of type INewBlogPost. Because your entity inherits from your binding filter interface, an instance of it will satisfy this requirement. The model binder will happily bind to the properties on the interface completely oblivious of any other properties your model object may have.
See this blog post for more information.
Aside
It is sometimes easy to run into action method ambiguity when you have two action methods with the same name; one for POST and one for GET like this:
[HttpGet]
public ActionResult NewBlogPost()
{
return View();
}
[HttpPost]
public ActionResult NewBlogPost()
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost);
if (ModelState.IsValid) { ... }
}
An easy way to fix that is to modify your POST action method to look like this:
[HttpPost]
public ActionResult NewBlogPost(FormCollection formCollection)
{
BlogPost newBlogPost = new BlogPost();
TryUpdateModel<INewBlogPost>(newBlogPost, formCollection);
if (ModelState.IsValid) { ... }
}
The MVC model binder knows how to bind the request form collection to an argument of type FormCollection so it will populate this just fine. Because your POST action now accepts an argument, it is no longer ambiguous with your GET method. You can pass this formCollection into TryUpdateModel to be used as the binding source if you wish, but you don't have to as it will default to the request form collection anyway. But since you are passing it in you may as well use it :)

ASP.Net MVC - model with collection not populating on postback

I have an ASP.Net MVC application with a model which is several layers deep containing a collection.
I believe that the view to create the objects is all set up correctly, but it just does not populate the collection within the model when I post the form to the server.
I have a piece of data which is found in the class hierarchy thus:
person.PersonDetails.ContactInformation[0].Data;
This class structure is created by LinqToSQL, and ContactInformation is of type EntitySet<ContactData>. To create the view I pass the following:
return View(person);
and within the view I have a form which contains a single text box with a name associated to the above mentioned field:
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
The post method within my controller is then as follows:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create (Person person)
{
//Do stuff to validate and add to the database
}
It is at this point where I get lost as person.PersonDetails.ContactInformation.Count() ==0. So the ModelBinder has created a ContactInformation object but not populated it with the object which it should hold (i.e ContactData) at index 0.
My question is two fold:
1. Have I taken the correct approach.. i.e. should this work?
2. Any ideas as to why it might be failing to populate the ContactInformation object?
Many thanks,
Richard
I think that your model is too complex for the default model binder to work with. You could try using multiple parameters and binding them with prefixes:
public ActionResult Create(
Person person,
[Bind(Prefix="Person.PersonDetails")]
PersonDetails details,
[Bind(Prefix="Person.PersonDetails.ContactInformation")]
ContactInformation[] info )
{
person.PersonDetails = details;
person.PersonDetails.ContactInformation = info;
...
}
Or you could develop your own custom model binder that would understand how to derive your complex model from the form inputs.
If a property is null, then the model binder other could not find it or could not find values in the submitted form necessary to make an instance of the type of the property. For example, if the property has a non-nullable ID and your form does not contain any data for that ID , the model binder will leave the property as null since it cannot make a new instance of the type without knowing the ID.
In other words, to diagnose this problem you must carefully compare the data in the submitted form (this is easy to see with Firebug or Fiddler) with the structure of the object you are expecting the model binder to populate. If any required fields are missing, or if the values are submitted in such a way that they cannot be converted to the type of a required field, then the entire object will be left null.
I've been struggling with this same type of scenario and eventually came to realize that the underlying problem is that the MVC default model binder does not seem to work on EntitySet<T> fields, only List<T> fields. I did however find a simple workaround that seems acceptable. In my case, I have a Company entity that has one to many relationship to Contacts (my Linq-to-Sql EntitySet).
Since it seems that when I change my code from EntitySet<Contact> to List<Contact>, the MVC default model binder starts working as expected (even though the LTS isn't now), I figured I would provide an alternate, "aliased" property to MVC that is of type List<Contact>, and sure enough, this seems to work.
In my Company entity class:
// This is what LINQ-to-SQL will use:
private EntitySet<Contact> _Contacts = new EntitySet<Contact>();
[Association(Storage="_Contacts", OtherKey="CompanyID", ThisKey="ID")]
public EntitySet<Contact> Contacts
{
get { return _Contacts; }
set { _Contacts.Assign(value); }
}
// This is what MVC default model binder (and my View) will use:
public List<Contact> MvcContacts
{
get { return _Contacts.ToList<Contact>(); }
set { _Contacts.AddRange(value); }
}
So now, in my View, I have the following:
<label>First Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].FirstName") %>
</label>
<label>Last Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].LastName") %>
</label>
Seems to work like a charm!
Best of luck!
-Mike
Maybe lack of Bind attribute is the case:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create ([Bind] Person person)
{
// Do stuff to validate and add to the database
}
The first argument of Html.TextBox is the name of the textbox, the second would be the value.
"Wrong":
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
"Right":
<%= Html.TextBox("nameoftextbox", person.PersonDetails.ContactInformation[0].Data)%>
Make sure your models (and all nested models) are using properties (getters/setters) instead of fields. Apparently the default binder needs properties to function properly. I had a very similar situation that was fixed by changing the necessary fields to properties.

Categories