MVC model binding naming convention for child objects? - asp.net

I'm having trouble with default model binding naming convention when there is a child property. For example:
I have a ViewModel which looks something like this:
public class UserViewModel
{
public User BusinessObject { get; set; }
}
My User class has a property called "NetworkLogin"
My View has something like this:
<%: Html.LabelFor(model => model.BusinessObject.NetworkLogin)%>
<%: Html.TextBoxFor(model => model.BusinessObject.NetworkLogin)%>
Auto-Fill
And my controller, what I'd like to do, is
[HttpGet]
public ActionResult UserIndex(string networkLogin) { }
The problem:
The input parameter "networkLogin" is always null. This makes sense, because the actual parameter on the html element is name="BusinessObject.NetworkLogin" and id="BusinessObject_NetworkLogin". However, I don't know what parameter name I should use in my action method. I've tried "businessObject_NetworkLogin" and it doesn't work either.
However, I have this workaround that does work, but I don't like it. I add this to my ViewModel:
public string NetworkLogin
{
get
{
if (BusinessObject == null)
BusinessObject = new User();
return BusinessObject.NetworkLogin;
}
set
{
if (BusinessObject == null)
BusinessObject = new User();
BusinessObject.NetworkLogin = value;
}
}
And my View page now says this instead.
<%: Html.TextBoxFor(model => model.NetworkLogin)%>
Can someone tell me what the proper naming convention is for default model binding so that I don't have to employ the above workaround?
Thank you!

Indicate the prefix so that the model binder knows that the BusinessObject.NetworkLogin query string parameter actually refers to networkLogin which is what you use as action argument
public ActionResult UserIndex(
[Bind(Prefix = "BusinessObject")] string networkLogin
)
{
...
}
or reuse your view model:
public ActionResult UserIndex(UserViewModel model)
{
// TODO: use model.BusinessObject.NetworkLogin
// which is gonna be correctly bound here
...
}
As far as your workaround is concerned, once you put one of my two suggestions into action your view model property should really look like this:
public string NetworkLogin { get; set; }

Related

Entering Value in Partial View and Posting it back to Main Controller in ASP.NET MVC 5 [duplicate]

I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!

ASP.NET MVC3: can Action accept mulitple model parameters?

For example:
class MyContoller
{
[MyCustomAttribute]
public ActionResult MyAction(ModelX fromRequest, ModelY fromSession, ModelZ fromCookie)
{
string productId = fromRequest.ProductId;
string userId = fromSession.UserId;
string cultureName = fromCookie.CultureName;
}
}
Reason:
I don't want to visit Request, Session and HttpContext in the controllers, and the default idea of MVC3 which passing models to actions is very great.
I want the number of parameters of MyAction is easy to change. For example, if I add a new parameter, the system will try to look for values in Request, Session or Cookies by the name or type of the parameter (I think custom ModelBinders may be required for cookie values) and pass the filled model to my action. I don't have to write extra code.
Can the custom attribute (MyCustomAttribute in the example) accomplish this idea?
I am not sure I follow you about the custom attribute. What are you expecting the custom attribute to do?
Yes, an action method can take as many model parameters as you want. Obviously, only one can be bound in any given request (because a view can only have one model). Whichever one is found first will be bound, and the others will be null.
So let's say you have the following:
public class ModelX {
public string X {get;set;}
}
public class ModelY {
public string Y {get;set;}
}
public class ModelZ {
public string Z {get;set;}
}
And you have an action method like this:
public ActionResult DoIt(ModelX x, ModelY y, ModelZ z)
{
return View();
}
And in your DoIt.cshtml you have the following:
#model ModelZ
#using(Html.BeginForm()) {
#Html.TextBoxFor(x => x.Z)
<input type="submit"/>
}
If you type something into the textbox and submit, then the model binder will bind a ModelZ with the value you entered and ModelX and ModelY will be null.
If you mean can an action method bind multiple models simultaneously, then I would have to ask you.. How exactly do you plan to have a view have more than one model? You can certainly create a wrapper model to contain the multiple models, but a view can only have one.
Create a composite ViewModel class that incorporates ModelX, ModelY and ModelZ. You can then populate an instance of your new ViewModel class, and pass that to your controller method.
public class XYZViewModel
{
public ModelX fromRequest { get; set; }
public ModelY fromSession { get; set; }
public ModelZ fromCookie { get; set; }
}
public class MyController
{
[MyCustomAttribute]
public ActionResult MyAction(XYZViewModel myModel)
{
string productId = myModel.fromRequest.ProductId;
string userId = myModel.fromSession.UserId;
string cultureName = myModel.fromCookie.CultureName;
}
}
You can always pass multiple parameters to your controller action, yes. The key is to make sure they are properly serialized in the request. If you're using a form, that means using the Html helper methods.
For example, let's say you want an action like this:
public ActionResult Multiple(ModelA a, ModelB b)
{
// ...
}
You could create simple partial view for each model:
#model MyProject.Models.ModelA
#Html.EditorForModel()
Then in your Multiple view, render the partial views like so:
#{ using (Html.BeginForm("Multiple", "MyController", FormMethod.Get))
{
#Html.Partial("A", new MyProject.Models.ModelA())
#Html.Partial("B", new MyProject.Models.ModelB())
<input type='submit' value='submit' />
}
I set the method to GET here so that you can easily see how MVC passes the parameters. If you submit the form, you'll see that MVC successfully deserializes each object.

How to set values for Model properties on .aspx page in ASP.NET MVC 2?

I have a strongly typed view. I get model passed into the view and then i assign model values to labels etc.
I would then also like to set Model values programmatically on .aspx page, like:
<%= Model.someValue = "foo"; %>
and then pass that model back to controller action and than access those values. I know that I can apply values to model like these:
<%= Html.TextBoxFor(n => n.someValue) %>
but in these case, this is not an option for me.
If the user is not supposed to modify the values of this model inside the view then you could use hidden fields or simply pass some unique identifier which will allow the controller action to retrieve back the model from the repository.
What information are you trying to set? You need to put them in form fields that will be POSTed back to the server. E.g.
public class MyModel
{
public string Name { get; set; }
public string UniqueCode { get; set; }
}
If can set properties if I need to:
<% Model.UniqueCode = "something"; %>
<%= Html.HiddenFor(m => m.UniqueCode) %>
And then accept those new values when the form is posted back:
public MyController : Controller
{
public Index()
{
return View(new MyModel { Name = "Hello" });
}
[HttpPost]
public Process(MyModel model)
{
string code = model.UniqueCode;
}
}
Though why is your view modifying the model?

Modern way to handle and validate POST-data in MVC 2

There are a lot of articles devoted to working with data in MVC, and nothing about MVC 2.
So my question is: what is the proper way to handle POST-query and validate it.
Assume we have 2 actions. Both of them operates over the same entity, but each action has its own separated set of object properties that should be bound in automatic manner. For example:
Action "A" should bind only "Name" property of object, taken from POST-request
Action "B" should bind only "Date" property of object, taken from POST-request
As far as I understand - we cannot use Bind attribute in this case.
So - what are the best practices in MVC2 to handle POST-data and probably validate it?
UPD:
After Actions performed - additional logic will be applied to the objects so they become valid and ready to store in persistent layer. For action "A" - it will be setting up Date to current date.
I personally don't like using domain model classes as my view model. I find it causes problems with validation, formatting, and generally feels wrong. In fact, I'd not actually use a DateTime property on my view model at all (I'd format it as a string in my controller).
I would use two seperate view models, each with validation attributes, exposed as properties of your primary view model:
NOTE: I've left how to combining posted view-models with the main view model as an exercise for you, since there's several ways of approaching it
public class ActionAViewModel
{
[Required(ErrorMessage="Please enter your name")]
public string Name { get; set; }
}
public class ActionBViewModel
{
[Required(ErrorMessage="Please enter your date")]
// You could use a regex or custom attribute to do date validation,
// allowing you to have a custom error message for badly formatted
// dates
public string Date { get; set; }
}
public class PageViewModel
{
public ActionAViewModel ActionA { get; set; }
public ActionBViewModel ActionB { get; set; }
}
public class PageController
{
public ActionResult Index()
{
var viewModel = new PageViewModel
{
ActionA = new ActionAViewModel { Name = "Test" }
ActionB = new ActionBViewModel { Date = DateTime.Today.ToString(); }
};
return View(viewModel);
}
// The [Bind] prefix is there for when you use
// <%= Html.TextBoxFor(x => x.ActionA.Name) %>
public ActionResult ActionA(
[Bind(Prefix="ActionA")] ActionAViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Name, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionBViewModel
}
}
public ActionResult ActionB(
[Bind(Prefix="ActionB")] ActionBViewModel viewModel)
{
if (ModelState.IsValid)
{
// Load model, update the Date, and commit the change
}
else
{
// Display Index with viewModel
// and default ActionAViewModel
}
}
}
One possible way to handle POST data and add validation, is with a custom model binder.
Here is a small sample of what i used recently to add custom validation to POST-form data :
public class Customer
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
public class PageController : Controller
{
[HttpPost]
public ActionResult ActionA(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
[HttpPost]
public ActionResult ActionB(Customer customer)
{
if(ModelState.IsValid) {
//do something with the customer
}
}
}
A CustomerModelBinder will be something like that:
public class CustomerModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "Name") //or date or whatever else you want
{
//Access your Name property with valueprovider and do some magic before you bind it to the model.
//To add validation errors do (simple stuff)
if(string.IsNullOrEmpty(bindingContext.ValueProvider.GetValue("Name").AttemptedValue))
bindingContext.ModelState.AddModelError("Name", "Please enter a valid name");
//Any complex validation
}
else
{
//call the usual binder otherwise. I noticed that in this way you can use DataAnnotations as well.
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
and in the global.asax put
ModelBinders.Binders.Add(typeof(Customer), new CustomerModelBinder());
If you want not to bind Name property (just Date) when you call ActionB, then just make one more custom Model Binder and in the "if" statement, put to return the null, or the already existing value, or whatever you want. Then in the controller put:
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerAModelBinder))] Customer customer)
[HttpPost]
public ActionResult([ModelBinder(typeof(CustomerBModelBinder))] Customer customer)
Where customerAmodelbinder will bind only name and customerBmodelbinder will bind only date.
This is the easiest way i have found, to validate model binding, and i have achieved some very cool results with complex view models. I bet there is something out there that i have missed, and maybe a more expert can answer.
Hope i got your question right...:)

How do I repopulate the view model in ASP.NET MVC 2 after a validation error?

I'm using ASP.NET MVC 2 and here's the issue. My View Model looks something like this. It includes some fields which are edited by the user and others which are used for display purposes. Here's a simple version
public class MyModel
{
public decimal Price { get; set; } // for view purpose only
[Required(ErrorMessage="Name Required")]
public string Name { get; set; }
}
The controller looks something like this:
public ActionResult Start(MyModel rec)
{
if (ModelState.IsValid)
{
Repository.SaveModel(rec);
return RedirectToAction("NextPage");
}
else
{
// validation error
return View(rec);
}
}
The issue is when there's a validation error and I call View(rec), I'm not sure the best way to populate my view model with the values that are displayed only.
The old way of doing it, where I pass in a form collection, I would do something like this:
public ActionResult Start(FormCollection collection)
{
var rec = Repository.LoadModel();
UpdateModel(rec);
if (ModelState.IsValid)
{
Repository.SaveModel(rec);
return RedirectToAction("NextPage");
}
else
{
// validation error
return View(rec);
}
}
But doing this, I get an error on UpdateModel(rec): The model of type 'MyModel' could not be updated.
Any ideas?
I figured this one out. If you call UpdateModel and there's a validation error, it's going to throw an exception. The way around this is call TryUpdateModel instead.
Your Price member setter (probably) shouldn't be public, you may want to consider loading the price from where ever it's stored in the model.
The other thing would be when rendering the view don't render the Price with a text box (or other input type).
public class MyModel
{
public decimal Price
{
get
{
return //get the value from something
}
} // for view purpose only
[Required(ErrorMessage="Name Required")]
public string Name { get; set; }
}
Using a strong type View
If you' use a strong type view this should work out of the box:
ViewPage<MyModel>
Your fields should be displayed as:
<%= Html.TextBoxFor(m => m.Name) %>
You shouldn't display read-only properties in editable fields anyway. When you'd redisplay the invalid view and providing the passed in model object instance in your controller action, your values should be populated in your textbox (or string only containers) as expected.
I don't think you should have any problems with Price property this way, but just in case have you tried using this controller action declaration:
public ActionResult Start([Bind(Exclude = "Price")]MyModel rec)
{
// ...
}

Resources