Using an Editor Template for a List - c#

I have something like this
public class ResumeVm
{
public ResumeVm()
{
EducationVms = new List<EducationVm>();
}
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<EducationVm> EducationVms { get; set; }
}
public class WorkExperienceVm
{
public string Title { get; set; }
}
I now want to make an editor template for each of the EducationVms, I made a Template for 1 Education Vm and tried to use
#Html.EditorForModel("WorkExperienceVm")
but it does not know how to pass in the EducationVms
If I do
#Html.EditorForModel("WorkExperienceVm", #Model.EducationVms )
It gets made as it expects only 1 Vm to be sent in.
// View (WorkExperienceVm)
#model ViewModels.WorkExperienceVm
#Model.Title

The EditorForModel overload that you're using is incorrect. EditorForModel produces an editor template for the current model (i.e. the ResumeVm) and the string you're passing in is the additional view data object, not the name of the view.
I'm assuming that "WorkExperienceVm" is the name of the view. Try using EditorFor:
#for(int i = 0; i < Model.EducationVms.Count; i++)
{
Html.EditorFor(m => m.EducationVms[i], "WorkExperienceVm")
}
An alternative is to create a template that's actually called EducationVm.cshtml and type it to EducationVm, then you can just do the following and the framework will figure out that you want the template called for each item in the collection:
#Html.EditorFor(m => m.EducationVms)
Unfortunately this approach can't be achieved using UIHints or passing in the view name manually into the helper, though that's fairly unlikely to get in your way if you don't mind adhering to strict naming conventions for your templates.
I wrote another answer a while ago explaining the differences between the different helpers for editor templates. It deals specifically with the "label" helpers but the same principles apply to the "editor" helpers.

Related

MVC abstract ViewModel, retain Validation Attributes (dynamically)

hopefully I'm missing something obvious, but I have a bit of an issue with some code I've written, and it's not feeling like I've written my code correctly.
So, let's say I have the following Model:
public class SampleViewModel {
[Required]
public string Property1 { get; set; }
[Required]
[EmailAddress]
public string Property2 { get; set; }
public IList<AbstractModel> Items { get; set; }
}
And then I have this abstract view model:
public abstract AbstractModel {
[Required(ErrorMessage = "This field is required")]
public virtual string Value { get; set; }
}
And these concrete view models:
public ConcreteModel1 : AbstractModel { }
public ConcreteModel2 : AbstractModel { }
Within my Controller, I have the following code (this is actually being done elsewhere, but for this sample, this is fine):
var model = new SampleViewModel();
var fields = new List<AbstractModel>() {
new ConcreteModel1() { Value = model.Property1 },
new ConcreteModel2() { Value = model.Property2 },
};
model.Fields = fields;
return View(model);
Within the SampleViewModel partial view (as I have one for each view model type), I have the following:
#model SampleViewModel
#for(var i = 0; i < Model.Items; i++) {
#Html.EditorFor(m => Model.Items[i])
}
Lets say that I also have a distinct partial view (with very different layout requirements) per each AbstractModel.
Example for the ConcreteModel1:
#model ConcreteModel1
#Html.TextboxFor(m => m.Value)
And for the ConcreteModel2:
#model ConcreteModel2
#Html.DisplayFor(m => m.Value)
This is all working, but as I've had to pass the ViewModel's properties (Property1) into the AbstractModel, I have lost the connection between the view and the underlying model. I have been able to bind the form fields back to the model, using a custom Model Binder, but the main thing I'm missing are the model validators which have been added to the SampleViewModel class.
Ideally I want this information to be available to the AbstractModel. Validation is happening, but I'm only getting basic validation on the client (via AbstractModel's Value required attribute), but I'd like to be able to pass along validation needs from my SampleViewModel into the AbstractModel.
Expectation
What I'd really like to happen is for the AbstractModel's Value property to somehow impersonate the property that is passed into it, so that it is just acting as a proxy to the original model, but has just reshaped the SampleViewModel (or specifically it's Property1 property).
So the important thing is, considering the following creation of my fields:
var fields = new List<AbstractModel>() {
new ConcreteModel1() { Value = model.Property1 },
new ConcreteModel2() { Value = model.Property2 },
};
How do the AbstractModels know that their Values are supposed to be: Required, and also Required and an EmailAddress, based on the properties that are used to create them?
Thank you for your input.

ASP.net MVC Is it possible to modify a class object in a view?

I am a newbie and creating a website where you can create your own custom quizes. Ive made a database that stores a class object mytests that consists of a name, and a list of questions parameter.
public class MyTests
{
public int ID { get; set; }
public string name { get; set; }
public string description { get; set; }
public List<MyQuestions> AllTestQuestions;
}
//using this object for questions
public class MyQuestions
{
public string QuestionDescription { get; set; }
public string MultipleChoiceCorrect { get; set; }
public string MultipleChoiceB { get; set; }
public string MultipleChoiceC { get; set; }
public string MultipleChoiceD { get; set; }
public string Answerexplanation { get; set; }
}
I'm using the default database code generated by visual studio. I have no problem adding this test object(mytest) to the database, but what I want to do is that on the edit.cshtml view I want to be able to add elements to the question list before returning the object to the database saved.
The problem is I don't know how to edit the model object from the view, or if this is even possible. I could maybe get it to work through a redirect? but I thought that adding the elements directly from the view would be easier. Is it possible to modify the model.object inside a view from the view (putting security concerns aside)?
For example model.title = something;
or
model.list.add()
Is anything like this possible?
If this question is not clear please let me know and I will try to clarify in the comments.
Yes, it is possible to edit the model from within the view.
From within your .cshtml file specify the view model using the #model declaration, then edit the model like so:
#model Namespace.For.MyTests
#Model.name = "Hello World";
<p>#Model.name</p>
Whilst this would work, it's not really what the view is for so I wouldn't recommend it.
The view is about presenting your data, not mutating it - that should be done in the controller, or domain layer. As soon as the user leaves the page then your changes will be lost due to the stateless nature of the web (.NET MVC passes data to the view from the controller, then ends the request).
This should be done at the controller level. You could do it on a view but it's not what the view is for.
Your issue is that if the page is refreshed you will lose you content, so if you do anticipate on the page refreshing you will need a way in which to temporarily hold the information before it being saved.
On a side note, I'd also consider renaming your classes "MyTests" to "MyTest" (singular) and "MyQuestions" to "MyQuestion"... it's just good practice because then you'd have a List of singleton "MyQuestion" in a "MyTest". EntityFramework Codefirst will pluralise the names when the database is created/update.

View expecting IEnumerable

Well im kinda new in Asp.net Mvc and im learning alone from scratch, i have a aplicattion that controls expends and earnings and what i am trying to do now is, basing on a list of earnings and expends give me the balance from a user, im having a lot of problems trying to control this and i dont know if i am doing it the right way
Here is my model:
public class Balance
{
public int BalanceId { get; set; }
public List<Expense> Despesas { get; set; }
public List<Earning> Rendimentos { get; set; }
public string ApplicationUserId { get; set; }
}
Soo what i did was, first trying to control when the user inserts a Earning or a row like, verifying if the User already exists on the database in the control method Create on the expenses and in the earning, if it doesnt exist he add the aplicationUserId and the expensive or the earning.
I want that the balance appears in every page, soo i added this to my Layout.cshtml
<li>#Html.Action("GetBalance", "Home")</li>
it calls the controller GetBalance:
public PartialViewResult GetBalance()
{
var userId = User.Identity.GetUserId();
var balance = db.Balance.Where(d => d.ApplicationUserId == userId);
return PartialView("_GetBalance",balance);
}
Send to the view _GetBalance the balance model:
#model <MSDiary.Models.Balance>
<p>Saldo: #GetBalance()</p>
#functions
{
HtmlString GetBalance()
{
decimal saldo = 0;
if (Model.Expense.Count != 0 || Model.Earning.Count != 0)
{
foreach (var item in Model.Despesas)
{
balance += item.EarningValue;
}
foreach (var item in Model.Rendimentos)
{
balance -= item.ExpenseValor;
}
}
return new HtmlString(balance.ToString());
}
}
What i want to know is, if there is a easyer way to do this, or what i can do to do what i want, i cant get it why my view expects something different can someone explain me what i am doing wrong?
Ps: Sorry for the long post and English, but i want to learn more :)
Firstly, the model #model <MSDiary.Models.Balance> needs to be changed to:
#model IEnumerable<MSDiary.Models.Balance>
Also, the method GetBalance should ideally be placed in a class not in GetBalance partial view. You could achieve this two ways, either through extension methods or have a Balance View Model that has the calculated balance as a property which is then passed down to your view.
As an example via an extension method:
public static class BalanceExtensions
{
public static string GetBalance(this Balance balance)
{
string displayBalance = "0:00";
// Your logic here
return displayBalance;
}
}
And then in your Partial View you can use the new HTML Helper:
#Html.GetBalance();
As an additional note I would change List to IEnumerable for expenses and earnings as it appears you are only exposing the data and not manipulating the data.
Your model would then look like:
public class Balance
{
public int BalanceId { get; set; }
public IEnumerable<Expense> Despesas { get; set; }
public IEnumerable<Earning> Rendimentos { get; set; }
public string ApplicationUserId { get; set; }
}
#Filipe Costa A few things here.
You should probably name your view the same thing as your method. The underscore preceding the name is fairly standard so I would suggest using that same name for the method. If the name of the method and view are the same you can simply pass in the model and not have to do the name + model signature of PartialView method. It's simpler.
Aside from that your code is fine but your .cshtml partial view should have this for the first line. That will accept the list you're passing.
#model IEnumerable<MSDiary.Models.Balance>
<h1>#Model.BalanceId</h1>
#*Do other stuff!*#

Using the collection's DisplayName from within DisplayTemplate for a single element

To begin with, the code we have no problems with. It's a model:
[DisplayName("A very hot hotshot")]
public Person Hotshot { get; set; }
[DisplayName("Just a developer")]
public Person Developer { get; set; }
[DisplayName("Some random guy")]
public Person RandomGuy { get; set; }
And then we have a view which looks like this:
#Html.DisplayFor(m=>m.Hotshot)
#Html.DisplayFor(m=>m.Developer)
#Html.DisplayFor(m=>m.RandomGuy)
DisplayTemplate for Person has a line which uses model's DisplayName:
#Html.DisplayNameForModel()
It's all nice, but the problem appears when you add a list property to the model and try to display it with DisplayFor. Here's the model part:
[DisplayName("Funny guys")]
public IEnumerable<Person> FunnyGuys { get; set; }
And, as DisplayFor is capable of displaying IEnumerable<T> iterating the T template, I'm calling it just like for other properties:
#Html.DisplayFor(m=>m.FunnyGuys)
It works great, except for fetching that DisplayName from the containing collection. It's set to null, since the attribute is on IEnumerable property, and the template gets a single element from it.
I had to use the workaround:
#Html.DisplayFor(m=>m.FunnyGuys, new {CollectionDisplayName = "Funny guys"})
And then using that property if DisplayName is null in Person template.
Is there a cleaner way?
I suppose DisplayNameFor helper is what are you looking for:
#Html.DisplayNameFor(m => m.FunnyGuys)
Also it's better to use Display attribute over DisplayName becose it's come from System.ComponentModel.DataAnnotations namespace and you can use not only literals in it but also resorses in your project. In case is you will localize your application.
[Display(Name = "Funny guys")]

ASP.net MVC - Binding a form value to a different name

I have a View that has the ability to both create and edit an item that I have separated into partial views:
MainEditView.cshtml
_CreateChildDialog.cshtml
_EditChildDialog.cshtml
I have separate ViewModels for both the Create and Child items:
public class CreateChildViewModel
{
public string ItemText { get; set; }
}
public class EditChildViewModel
{
public string ItemText { get; set; }
}
Since the partial views for the Edit and Create dialog boxes will both be rendered on the same page, I will have a conflict for form id's and names...since they are both called ItemText.
Is it possible to customize the binding of these elements without writing a custom model binder?
I would like to do something like:
public class EditChildViewModel
{
[BindFrom("EditItemText")]
public string ItemText { get; set; }
}
Or does it just make more sense to rename the ViewModel properties to:
public class EditChildViewModel
{
public string EditItemText { get; set; }
}
public class CreateChildViewModel
{
public string CreateItemText { get; set; }
}
EDIT
Based on converstation with Darin I want to make this a little more clear.
My Parent has an Edit action.
When you edit the Parent, you would never create a new child or edit a child when you are calling the ParentController.Edit action.
I have a separate controller for the Child object that has a Create and Edit method:
public class ChildController
{
public ActionResult Edit() {}
public ActionResult Create() {}
}
I am using jQuery calls to asynchronously post to this controller when you edit or create a child. Basically I use a jquery dialog to create/edit a child that will get saved immediately when I click Ok on the dialog. This would happen even before clicking save for the Edit action of the parent.
I would use editor templates. Normally you would pack those two view models into a main view model which will be used by the main view:
public class MyViewModel
{
public CreateChildViewModel Create { get; set; }
public EditChildViewModel Edit { get; set; }
}
and then:
#model MyViewModel
#Html.EditorFor(x => x.Create)
#Html.EditorFor(x => x.Edit)
and I would replace the two partials by their corresponding editor templates (~/Views/Shared/EditorTemplates/CreateChildViewModel.cshtml and ~/Views/Shared/EditorTemplates/EditChildViewModel.cshtml). The editor templates will take of generating proper names and ids of the corresponding input elements.
Personally I tend to prefer editor/display templates instead of partials as they handle better naming of input elements.

Categories