ASP.NET MVC 2: Using duplicate input partial views without duplicate validation - asp.net

In an application being rewritten, originally a certain form included a drop down where the user chose one of two major options for how the input would be used in calculations elsewhere.
The requirements now dictate that instead of this drop down, the interface should feature two otherwise identical forms appearing on the same page, one above the other. A parameter or hidden value is to determine whether the aforementioned option is set on each of the forms so that there is one form for each of the two options. This sets a boolean value on the model.
I'm using the same partial view for both appearances of the form, defining their differences during initialization. However, I'm still having one particular issue--if there is a validation error on one form, it appears on both. What's the best way to prevent this?
Am I just going to have to give in and make near-duplicate partial views, or is there a way to keep using the same one?

You could try something like this:
Create a base model for the form. That base model will have the properties and validation attributes that are common to its two child models:
public class BaseModel {
[Required]
public string Name { get; set; }
}
public class Model1 : BaseModel {
public bool Form1 { get; set; }
}
public class Model2 : BaseModel {
public bool Form2 { get; set; }
}
You can then create two different controller actions that accept those models as parameters:
public ActionResult PostForm1(Model1 model) { }
public ActionResult PostForm2(Model2 model) { }
And your partial view would have to add an input depending on which form it is:
<form action="<%=(isForm1 ? "/PostForm1" : "/PostForm2")>%">
<input type="hidden"
id="<%=(isForm1 ? "Form1" : "Form2")%>"
name="<%=(isForm1 ? "Form1" : "Form2")%>"
value="true" />
</form>
When a form gets posted, it should only do validation on its model and leave the other model untouched (you'd need a view model that has both form models as properties).
I'm not sure if any of this would work, but, again, its something you could try.

Related

Is using Viewbag for remembering past form inputs a bad idea?

I have a small asp.net core mvc application that basically consists of a form that a user can input some constraints into, and then get a filtered list of data depending on those constraints.
The controller action for filtering data basically looks like this:
[HttpPost]
public async Task<IActionResult> Query(QueryModel query)
{
var customers = await _context.Customers.AsQueryable().FilterCustomerList(query);
return View("Index", customers);
}
Now, my issue is that I would like the inputs in the fields to persist after entering them and being redirected to the view again. Right now they are currently just reset.
One way of doing this that I found was using viewBag. An example for a single query attribute is this:
public async Task<IActionResult> Query(QueryModel query)
{
var customers = await _context.Customers.AsQueryable().FilterCustomerList(query);
ViewBag.Name = query.Name;
return View("Index", customers);
}
and then the inpuit html elelment would look like:
<div class="col-md-4">
<input name="Name" type="text" placeholder="First name" value="#ViewBag.Name"class="form-control">
</div>
And this makes sure that if something has been entered into a field, it will now be entered into the field when after the query has been submitted.
But when I read up on ViewBag, I understand that a lot of .net developers have an aversion to it. It's not safe, the compiler can't catch errors in it easily etc.
Also, If I were to add all the input fields in my form to the viewbag, I would need a lot of lines of ViewBag.Attribute = query.SomeAttribute (20-30). Which seems like a code-smell too.
Is there any nicer way to do what I am trying to here?
You haven't included your QueryModel class and that class could be a key point to a cleaner approach.
You see, usually the user data, POSTed to your action is bound to the model, from there it's rendered on the form and is POSTed again. The model binding is where an input of a specific name is bound to a model member of the same name.
Thus, there's no need for viewbags.
More formally:
The Model
public class QueryModel
{
[your-validators-in-attributes, e.g. Required or MaxLength
there can be multiple validators]
public string Name { get; set; }
}
The controller:
[HttpPost]
async Task<IActionResult> Query(QueryModel query)
{
// query.Name is there, bound from the view
}
The View:
#model .....QueryModel
<div>
#Html.TextBoxFor( m => m.Name, new { placeholder = "a placeholder" } )
</div>
The html helper does two things
renders an input of the given name (Name in this case)
sets its value depending on the actual value from the model
In newer ASP.NETs you can achieve similar result by using tag helpers, where instead of Html.TextBoxFor(...) you write
<input asp-for="Name" />
These two approaches, using html helpers or using tag helpers are equivalent. In both cases there's no need for view bags.

How to get translated ModelState error message using jQuery in Asp.net Core 2?

I'm trying to validate HTML forms in an Asp.net Core 2.0 application, using Data Annotations, jQuery Validation and jQuery Unobtrusive validation, as strongly suggested by the docs. I also have some custom Javascript to display the error messages in a notification window.
I have a model that looks like this:
class Model
{
[Required(ErrorMessage = "Required")]
[Display(Name = "Email")]
[EmailAddress(ErrorMessage = "ValidEmail")]
public string Email { get; set; }
}
After many attempts, I got Asp.net to emit an input tag ready for validation with properly localized error messages:
<input class="form-control input-validation-error" type="email"
data-val="true" data-val-email="L'adresse e-mail est invalide."
data-val-required="Le champ E-mail est obligatoire." id="Email"
name="Email" value="test#example" aria-describedby="Email-error">
I have managed to get validation working client-side, before submitting the form. jQuery Validation correctly detects an empty field and I can display the message from data-val-required using Javascript. For the sake of completeness, I have something like this handling the message:
function onFormValidationFailed(e, args: JQueryValidation.Validator) {
ErrorManager.clearErrors();
for (var error in args.errorList) {
ErrorManager.addError(args.errorList[error].message);
}
}
$("form")
.on("invalid-form.validate", onFormValidationFailed);
However, a value such as test#example is accepted by the client-side validation and so the form is submitted. The error is then caught by Asp.net model validation. In my action I can see that this.ModelState.IsValid is indeed false and, as you can see above, the field was correctly identified as having an error with the class input-validation-error.
My problem is that I cannot figure out how to obtain the pertinent error message to display it.
I tried to call $("form").validate() when the page is loaded to try to trigger validation, but I'm not getting any error messages. (Or at least the invalid-form.validate event is not being triggered.)
As a fallback solution, I tried using <div asp-validation-summary="All"></div> to have Asp.net inject the error messages somewhere I could get them with Javascript. But this soon turned out to be a dead end because the message is not being localized!
<div class="validation-summary-errors" data-valmsg-summary="true">
<ul>
<li>ValidEmail</li>
</ul>
</div>
And indeed, I checked on the server side and the ModelState collection does not contain the localized message.
Can anyone help me find a solution where, upon coming back from the server with an invalid model state, I can get, via Javascript, the pertinent localized data annotation error messages so I can display them to the user? (Or suggest a better practice?)
I found the answer to my problem and, although it is a little embarrassing, I share it here in case other people find themselves in a similar situation.
The actual problem
It turns out the code I was working on wasn't exactly like described above, but I didn't notice that until I tried to create a stripped-down version to reproduce.
The model looked rather something like this:
class Model : ModelData
{
}
class ModelData
{
[Required(ErrorMessage = "Required")]
[Display(Name = "Email")]
[EmailAddress(ErrorMessage = "ValidEmail")]
public string Email { get; set; }
}
The view had code like this:
#model MyApp.Model
<input asp-for="Email" />
And the controller, had a couple of methods like this:
[HttpGet]
public IActionResult Edit()
{
return View(new Model());
}
[HttpPost]
public IActionResult Edit(ModelData data)
{
if (ModelState.IsValid)
{
data = _service.Save(data);
}
var model = new Model
{
Email = data.Email
};
return View(model);
}
My resx file was called Model.resx.
The root cause
what I had missed was the inheritance in the model!
Once I noticed that, everything made sense:
When building the view, the model, of type Model found the translations in Model.resx
When validating in the action though, the validated type is no ModelData and the resource manager couldn't find translations for that type since there was no ModelData.resx
The fix
What I did to fix my problem is prefer composition over inheritance. My model now looks like this:
class Model
{
public ModelData Data { get; set; }
}
The view like this:
#model MyApp.Model
<input asp-for="Data.Email" />
And moved all relevant translations to ModelData.resx. Since ModelData is the type I'm building inputs for and validating in the controller action, resources are always handled correctly.

Partial Views with view models different to the main view

My home-screen has a view model of DashboardViewModel. It has PartialViews with their own ViewModels such as CustomerSearchViewModel and SelectProductViewModel.
All three ViewModels are separate.
When I run the application I get this error:
The model item passed into the dictionary is of type
'Invoice.Web.ViewModels.DashboardViewModel', but this dictionary
requires a model item of type
'Invoice.Web.ViewModels.SearchCustomerWindowVM'.
I wonder what should I do to resolve this issue.
As planned, the Home screen will eventually integrate a lot of PartialViews with their own view models. Do I declare the Partial-view-models inside the DashboardViewModel? or do I simply have a single big DashboardViewModel for all partialViews to come?
You can have your partial view viewmodels as properties of your main viewmodel and call Html.Partial and pass these properties.
public class DashBoardVM
{
public string Name { set;get;}
public CustomerSearchVM CustomerSearch { set; get;}
public DashBoardVM()
{
CustomerSearch =new CustomerSerachVM();
}
}
In your dashboard view,
#model DashBoardVM
<h2>#Model.Name</h2>
#Html.Partial("CustomerSearch",Model.CustomerSearch)
Assuming CustomerSearch partial view is strongly typed o CustomerSearchVM class.
Another option is to use Html.Action() or Html.RenderAction(). This allows you to call a completely separate controller from your parent view, and return a completely different, non associated model. Here is a pretty good explanation on both rendering Partial Views and Actions. http://www.midnight-coding.com/2013/01/partial-vs-action-vs-renderpartial-vs-renderaction.html

How do I use two models on a shared layout page in MVC 3 ASP.NET?

I have a main layout page that has a basic table setup. I have a Main Content cell that I use to put RenderBody() in. I have product listings that use a Products database and I have a menu on the right side that needs to access the User database. How can I list my products using the products model in the Main Content cell and use the users model to list info in my Menu cell?
The only method I have used (however I'm not entirely confident on it's best practice status) is to create a model for the page that includes both (in your case) the product model and the user model. Populate these models in the controller, then access the inner models as you normally would in the view.
You can use partials to render content sections.
Imagine your page uses this model:
public class PageModel {
public string Title { get; set; }
public ProductListModel Products { get; set; }
}
Then you can render your partial view and pass in the model for that:
#Html.Partial( "name of partial view", Model.Products )
A slightly more performance costly approach is to call an action to render the partial view:
public class ProductsController {
public ActionResult List() {
var model = new ProductListModel();
return View( "your partial view", model );
}
}
And in your view:
#Html.Action( "list", "products" )
The advantage of the latter approach is that you keep your page model relatively clean, so that it doesn't have to contain all the data needed to display the page.

Strongly typed form in MVC which maps to a different type?

When I specify form inputs with the #Html.TextBoxFor method, then the generated input element would not necessarily map to the type that is expected in the form's action method.
Let's say I have two classes:
public class HomeA
{
public int A { get; set; }
}
public class HomeB
{
public int B { get; set; }
}
HomeA is the model of my view. If a controller action expects HomeB, then I can't provide the necessary input element in a strongly typed manner in my form:
#using (Html.BeginForm())
{
#Html.TextBoxFor(model => model.A)
}
This form will obviously not map to HomeB's property.
The controller action should not expect HomeB.
Use one view model per action.
If you are sending a ViewModel of XYZ, then in general your ActionMethod takes a ViewModel of XYZ.
Thats my general thoughts anyways for consistency/readability.
However if it works for you, do it as long as the relation is there.
ASP.net MVC - One ViewModel per View or per Action?
As for the note on composition vs. inheritance check out
ASP.NET MVC Architecture : ViewModel by composition, inheritance or duplication?
Check out
http://lostechies.com/jimmybogard/2009/04/24/how-we-do-mvc/
You would create a HomeAB class that contains both a HomeA and HomeB
If you have to create and to show some items which belong to both classes A & B, you can design an interface and then inherit that interface. Or you can create another class AB which inherits from A & B.
Hope this helps!

Resources