asp.net mvc 2 multiple selection listbox. how to read values in controller - model-view-controller

I'm having a hard time getting a fairly simple binding to work.
When editing an employee I want to display a listbox containing all the available roles in the system, (the employee's roles selected), and when saving the employee I want the selected roles to get populated by MVC into the Employee object which comes as an input param to the controller.
I know I can read the comma-separated values from Request.Form, but I'd rather not touch the Request object directly in my controllers as this makes them harder to test.
What is the best way to get MVC to supply me with the list of roles as an input param?

You have two options. The first is to get the values in the controller and put them into the ViewData dictionary, like so:
var list = EmployeeRoles.GetAll();
ViewData["EmployeeRoles"] = list;
Then you access it in the View using:
<%= (List<EmployeeRoles>)ViewData["EmployeeRoles"] %>
This way is kind of ugly because the view then has to know that it can use a 'magic string' to get an object out of the ViewData dictionary, which then has to be casted back into its original type.
The second way is more elegant, but introduces a little more complexity into your code. You create a ViewModel class, or basically a class that encapsulates all the data you want:
public class EmployeeViewModel
{
public Employee Employee { get; set; }
public ICollection<EmployeeRoles> EmployeeRoles { get; set; }
public EmployeeViewModel(Employee employee, ICollection<EmployeeRoles> roles)
{
Employee = employee;
EmployeeRoles = roles;
}
}
Then you would pass it from the controller to the view like so:
return View(new EmployeeViewModel (employee, roles);
On the view side, you'd access it like any other model:
<%= Model.Employee.Name %>
<%= Model.EmployeeRoles.First() %>
This method is more testable, but then you'd have to make a new ViewModel class for anything that requires data from more than one source.
As for returning the data from the view to the controller, the default model binder is actually quite good. You just have to use a HtmlHelper on the View to let it know what it should send the value back as. I don't have my book in front of me right now, but it should be something like this for a textbox:
<% Html.TextBox("Name", Model.Employee.Name) %>
The HtmlHelper will figure out what it needs to send back in order for the model binder to bind it correctly. I don't know what it is for a DropDownBox off the top of my head though.

Related

How do I bypass the limitations of what MVC-CORE controllers can pass to the view?

From what I've read, I'm supposed to be using ViewModels to populate my views in MVC, rather than the model directly. This should allow me to pass not just the contents of the model, but also other information such as login state, etc. to the view instead of using ViewBag or ViewData. I've followed the tutorials and I've had both a model and a viewmodel successfully sent to the view. The original problem I had was that I needed a paginated view, which is simple to do when passing a model alone, but becomes difficult when passing a viewmodel.
With a model of
public class Instructor {
public string forename { get; set; }
public string surname { get; set; }
}
and a viewmodel of
public class InstructorVM {
public Instructor Instructors { get; set; }
public string LoggedIn { get; set; }
}
I can create a paginated list of the instructors using the pure model Instructor but I can't pass InstructorVM to the view and paginate it as there are other properties that aren't required in the pagination LoggedIn cause issues. If I pass InstructorVM.Instructors to the view, I get the pagination, but don't get the LoggedIn and as this is just the model, I may has well have passed that through directly.
An alternative that was suggested was to convert/expand the viewmodel into a list or somesuch which would produce an object like this that gets passed to the view
instructor.forename = "dave", instructor.surname = "smith", LoggedIn="Hello brian"
instructor.forename = "alan", instructor.surname = "jones", LoggedIn="Hello brian"
instructor.forename = "paul", instructor.surname = "barns", LoggedIn="Hello brian"
where the LoggedIn value is repeated in every row and then retrieved in the row using Model[0].LoggedIn
Obviously, this problem is caused because you can only pass one object back from a method, either Instructor, InstructorVM, List<InstructorVM>, etc.
I'm trying to find out the best option to give me pagination (on part of the returned object) from a viewmodel while not replicating everything else in the viewmodel.
One suggestion was to use a JavaScript framework like React/Angular to break up the page into a more MVVM way of doing things, the problem with that being that despite looking for suggestions and reading 1001 "Best JS framework" lists via Google, they all assume I have already learned all of the frameworks and can thus pick the most suitable one from the options available.
When all I want to do is show a string and a paginated list from a viewmodel on a view. At this point I don't care how, I don't care if I have to learn a JS framework or if I can do it just using MVC core, but can someone tell me how to do this thing I could do quite simply in ASP.NET? If it's "use a JS framework" which one?
Thanks
I'm not exactly sure what the difficulty is here, as pagination and using a view model aren't factors that play on one another. Pagination is all about selecting a subset of items from a data store, which happens entirely in your initial query. For example, whereas you might originally have done something like:
var widgets = db.Widgets.ToList();
Instead you would do something like:
var widgets = db.Widgets.Skip((pageNumber - 1) * itemsPerPage).Take(itemsPerPage).ToList();
Using a view model is just a layer on top of this, where you then just map the queried data, no matter what it is onto instances of your view model:
var widgetViewModels = widgets.Select(w => new WidgetViewModel
{
...
});
If you're using a library like PagedList or similar, this behavior may not be immediately obvious, since the default implementation depends on having access to the queryset (in order to do the skip/take logic for you). However, PagedList, for example has StaticPagedList which allows you to create an IPagedList instance with an existing dataset:
var pagedWidgets = new StaticPagedList<WidgetViewModel>(widgetViewModels, pageNumber, itemsPerPage, totalItems);
There, the only part you'd be missing is totalItems, which is going to require an additional count query on the unfiltered queryset.
If you're using a different library, there should be some sort of similar functionality available. You'll just need to confer with the documentation.

which is the best practices for exposing entity or DTO to view in mvc3?

I have created my own customized lots of architecture including n-tier layers for different different technology.
Currently working on n-tier architecture with asp.net mvc framework. The problem is I have entity framework at data access layer. As the entities will have all it's relational metadata and navigation properties, it becomes heavier one. I am feeling like it is not wise to expose this entities directly over mvc view.
I am more favor in exposing own customized model of entities over mvc view which one be lighter one.
But this also leads me overhead of converting data from my original entities to customized model.
For example I have Employee entity which is as generated from edmx file of entity framework. It contains total 20 fields with all navigation properties.
Now over view in mvc I need to show only 2 fields for edit.
So do we need to expose original entity to view or need to create DTO/customized model of that two field and than expose that view?
I would use a view model. I have learnt not to expose my domain objects to the view, I rather map my domain object to the view model and return this view model to the view.
Here is a partial view model, you might have more properties if you need more employee data to create/edit or display:
public class EmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
In my action method of my controller it would look something like this:
public ActionResult Edit(int id)
{
Employee employee = employeeRepository.GetById(id);
// Mapping can be done here by using something like Auto Mapper, but I am
// manually mapping it here for display purposes
EmployeeViewModel viewModel = new EmployeeViewModel();
viewModel.FirstName = employee.FirstName;
viewModel.LastName = employee.LastName;
return View(viewModel);
}
And then your view might look something like this:
<td>First Name:</td>
<td>#Html.TextBoxFor(x => x.FirstName, new { maxlength = "15" })
#Html.ValidationMessageFor(x => x.FirstName)
</td>
I prefer to have a view model that has only the values of employee that is needed on the view. Let say your employee has 20 properties, and you only need to update 2 fields, why then pass all 20 to the view? Use only what you need.
You can expose original entity, that is nothing bad. But as soon as you need some other information on the view e.g. instead of Name and Lastname you need FullName, then you should create an EmployeeViewModel with only needed properties. You initialize it with desired values in an action and then pass it to the view.

Passing IEnumerable<Object> to ViewModel - Does Object need to be ViewModel?

Getting my head around MVC today and ran across the best practice of not passing a Model directly to a view. Instead, use a ViewModel.
I researched AutoMapper and plan on using it to map my ViewModels to the respective Models. And I understand that AutoMapper is smart enough to map IEnumerable to IEnumerable without a separate mapping, as long as source and dest are mapped.
But I'm a bit confused about how to handle passing an IEnumerable in my ViewModel to my view. I currently have my page working using a ViewModel that includes IEnumerable but I read that this is just as bad as passing the IEnumerable directly to the view. So do I need a separate ViewModel to hold the object which will be used in an IEnumerable property of the main ViewModel?
So where Activity is the Model in question:
public class ActivityHistoryViewModel
{
public IEnumerable<Activity> activities { get; set; }
}
do I need to create ActivityViewModel and write my ActivityHistoryViewModel like this?
public class ActivityHistoryViewModel
{
public IEnumerable<ActivityViewModel> activities { get; set; }
}
Is there an easier way to do this?
Yes, this is correct. Assuming the only data your model will need is the list, then you don't really need ActivityHistoryViewModel and the view can be typed as such:
#model IEnumerable<ActivityViewModel>
your auto mapper config would look like this:
Mapper.CreateMap<Activity, ActivityViewModel>();
you would map like this:
IEnumerable<Activity> data = GetActivities();
var model = Mapper.Map<IEnumerable<Activity>, IEnumerable<ActivityViewModel>>(data);
return View(model);
And when you define ActivityViewModel you can either create a property-for-property duplicate type, or trim out the excess data you don't need (in my case it would be something like "created date", that is db generated and of no importance to users).
Or if you want to stick with ActivityHistoryViewModel to pass along more than just the list:
view type:
#model ActivityHistoryViewModel
mapping config can remain the same
map like this:
IEnumerable<Activity> data = GetActivities();
var model = new ActivityHistoryViewModel() {
someOtherProperty = "hello world!",
activities = Mapper.Map<IEnumerable<Activity>, IEnumerable<ActivityViewModel>>(data)
};
return View(model);

A `ViewModel` for each page (`Create.cshtml` and `Edit.cshtml`)?

Questions
There are actually two related questions:
Should I create a ViewModel for each page?
If you do not have problems in creating a single ViewModel class for the two pages (Create.cshtml and Edit.cshtml) how can I validate the ViewModel in different ways (depending on the page that is being used)
Source
ViewModel
public class ProjectViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
}
Edit.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = Model.Name;
}
#Html.EditorForModel()
Create.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = "New Project";
}
#Html.EditorForModel()
ProjectValidator.cs
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
private readonly IProjectService _projectService;
public ProjectValidator(IProjectService projectService)
{
_projectService = projectService;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("required field")
/*The validation should be made only if the page is Create.cshtml. That is, if you are creating a new project.*/
.When(p => p.??) //Problem Here!!
.Must(n => !_projectService.Exist(n)).WithMessage("name already exists");
RuleFor(p => p.Url)
.NotEmpty().WithMessage("required field");
}
}
Note that if the user is editing an existing project, validation of the property name should not be done again.
ProjectController.cs > Edit method
[HttpPost]
public ActionResult Edit(Guid projectID, ProjectViewModel model)
{
var project = _projectService.Repository.Get(projectID);
if (ModelState.IsValid && TryUpdateModel(project))
{
_projectService.Repository.Attach(project);
if (_projectImageWrap.Create(project) && _projectService.Repository.Save() > 0)
return AjaxRedirect("Index");
}
return View(model);
}
Notes
If I create a ViewModel for each page, there is a duplication of code since pages have the same properties.
Add a property on the ViewModel indicating what page it is being displayed does not solve my problem as to instantiate the ViewModel, I use AutoMapper.
To validate the data, I use FluentValidator.
Thank you all for your help!
My understanding is that there isn't a 1:1 correlation between ViewModels and Views. Oftentimes you will have a View that will not require a ViewModel to go alongside with it.
You will want to create a ViewModel if and only if you need a Model absolutely paralleled and tailored to a specific View. This will not be the case 100% of the time.
When the functionality / use case /validation is different between the pages I use different models. If its the exact same besides the presence of an ID or something similar I use the same model, and its also possible to just use the same view if the differences are pretty minor.
Since your validation is different, if I were doing it I would create two different models so that I could use the out of the box DataAnnotations, with your validation though it may not be required. You could also on the edit model have a readonly property for name since its not editable any longer.
For me the same object must have the same validation on every time, in main to ensure the consistence of the object, independently if it was created or edited.
i think that you should create only one validation, and edit your "exists" method to pass to verify if it is a new object or the current object in repository.
Personally, I don't have a problem with 2 view models, especially if (as Paul Tyng suggested) you use a base class for the fields that are common to edit and create scenarios.
However, if you really only want a single view model then you would either need to:
add a flag to the view model and use the When() method in your validator. Note though that this will not generate the appropriate client-side only validation
define a second validator and invoke the appropriate one from the controller (i.e. instead of the "automatic" validation)
Provide another view Edit.cshtml which will allow the user to edit the data for a selected item.
Create another view Query.cshtml which based on the ItemName will allow the users to query the Inventory table.
Perform the calculation for the total profit (numbersold times (saleprice-purchasecost). Display the total profit.
(BONUS) Create another view Sell.cshtml that will indicate the sale of an item. Adding one to NumberSold and subtract one from NumberInventory for the selected record.

Using one Partial View Multiple times on the same Parent View

I am using MVC3 razor. I have a scenario where I have to use a partial view multiple times on the same parent view. The problem I am having is that when the Parent View gets rendered, it generates same names and ids of the input controls within those partial views. Since my partial views are binded to different models, when the view is posted back on "Save" it crashes. Any idea how can i make the control id/names unique, probably some how prefix them ?
Awaiting
Nabeel
Personally I prefer using editor templates, as they take care of this. For example you could have the following view model:
public class MyViewModel
{
public ChildViewModel Child1 { get; set; }
public ChildViewModel Child2 { get; set; }
}
public class ChildViewModel
{
public string Foo { get; set; }
}
and the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
Child1 = new ChildViewModel(),
Child2 = new ChildViewModel(),
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and inside the Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<h3>Child1</h3>
#Html.EditorFor(x => x.Child1)
<h3>Child2</h3>
#Html.EditorFor(x => x.Child2)
<input type="submit" value="OK" />
}
and the last part is the editor template (~/Views/Home/EditorTemplates/ChildViewModel.cshtml):
#model ChildViewModel
#Html.LabelFor(x => x.Foo)
#Html.EditorFor(x => x.Foo)
Using the EditorFor you can include the template for different properties of your main view model and correct names/ids will be generated. In addition to this you will get your view model properly populated in the POST action.
There is an alternative:
Add a prefix to the PartialView
Bind the model, removing the prefix
For 1, set the prefix in your View:
ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "prefix";
For 2, you can recover the data with UpdateModel, like this:
UpdateModel(producto, "prefix");
This is not very advisable because your action doesn't receive the data as a parameter, but updates the model later. This has several inconvenients: 1) it's not clear what your action needs by looking at its signature 2) it's not easy to provide the input to the action for unit testing it 3) the action is vulnerable to overflow parameters (parameters provided by the user that shouldn't be there and are mapped to the model).
However, for 2 there is an alternative: register a custom Model Binder that allows you to do remove the prefix. And the custom Model Binder must know about it.
A good solution is in this SO Q&A: How to handle MVC model binding prefix with same form repeated for each row of a collection? But it has a little flaw: if you add a hidden field with the name "__prefix" in a partial view, and you render it several times as a partial view, this ID will be repeated for several different elements in the page, which is not allowed, and can provoke some trouble. And one of the most important reasons to provide a prefix is precisely rendering the same "edit" view as partial views for several instances of an entity. I.e. this would happen in a page like gmail, where you can edit several emails at once.
There are several possible solutions for this problem.
One of them is providing the prefix as a query string or routedata value, and not as a form field, which avoid the Id conflicts, and can be found by the model binder. (It can always have the same name).
Another solution is to use a hidden field, with a fixed pattern, but which is different for every rendered view. The prefix could follow this pattern for uniqueness: "PP$ActionControllerId" like "PP$EditProduct23", which is unique for each rendered view, and can be easily found between the request parameters looking for one that starts with "PP$".
And a final solution would be to create the prefix only in the view, and not providing it in any kind of request parameter. The Model binder would have to look for the prefix examining the names of the request parameters, until it finds one whose prefix follow the pattern.
Of course, the custom ModelBinder must be adapted to work tieh the chosen convention.

Resources