Reshape a viewmodel to a model - c#

On client side I display data in a way customized for the view needs.
When the user saves and sends the data to the server I do NOT want to reshape the data on
client side to fit the requirements of my domain.
To reshape my ViewModel to my Model I COULD do this:
var periods = periodListViewModel.CellViewModels
.SelectMany(c => c.Rows)
.Select(
p =>
new Period { LessonNumber = p.LessonNumber });
This code must be put at a place of the correct responsibility.
These are my classes:
public class SavePeriodListViewModel
{
public int SchoolyearId { get; set; }
public IEnumerable<CellViewModel> CellViewModels { get; set; }
}
public class CellViewModel
{
public CellViewModel()
{
Rows = new List<RowViewModel>();
}
public List<RowViewModel> Rows { get; set; }
}
public class RowViewModel
{
public string DayName { get; set; }
public string SchoolclassCode { get; set; }
public string Content { get; set; }
public int LessonNumber { get; set; }
}
public class LessonplannerResponse
{
public int SchoolyearId { get; set; }
public IEnumerable<Period> Periods { get; set; }
}
All data I need for my model Period is inside the RowViewModel.
I need to reshape my SavePeriodListViewModel to the LessonplannerResponse
Of course AutMapper can handle the RowViewModel to Period mapping but my problem is not in that area.
AutoMapper is mapping not reshaping collections.
Where should I put the linq reshaping code? Or more general asked where should I put such code in asp.net mvc ?
client side?
controller action?
service is no option as it should not know the viewmodel.

It would appear that the mapping needs to happen inside the controller. I would expect that the Controller Action is defined something like:
public ActionResult SomeAction(SavePeriodListViewModel model)
{
// transform from model to LessonplannerResponse
// validate model
// do something with the model and return a view
}
From the look that we have into your processes, it doesn't appear to be the responsibility of a service to do the transformation, nor does it appear to be the responsibility of the client side -- especially since you already ruled out the client side in your question.

Here is example of how i do it to ViewModel you can also implement it visa verse.
Mapper.CreateMap<Faq, FaqViewModel>()
.ForMember(x => x.Answer, opt => opt.MapFrom(src => src.Answer))
.ForMember(x => x.Question, opt => opt.MapFrom(src => src.Question));
var sourceOfModels = _repository.GetFaqListByAppId(id);
var resultOfViewModels = Mapper.Map<List<Faq>, List<FaqViewModel>>(list.ToList());
This is works perfect for me.

Related

Query items inside collection inside collection

I have the following classes in my .net core 2.0-mvc app:
public class TeamMatch
{
public int ID { get; set; }
public virtual ICollection<IndividualMatch> Matches { get; set; }
public IEnumerable<Map> GetMaps()
{
var list = new List<IndividualMatch>();
list.AddRange(Matches.GroupBy(m => m.MatchMap.ID).Select(u => u.First()));
return list.Select(m => m.MatchMap);
}
}
public class IndividualMatch
{
public int ID { get; set; }
public Map MatchMap { get; set; }
}
public class Map
{
public int ID { get; set; }
public string Name { get; set; }
}
And this gets passed from the Controller to the View:
public IActionResult Index()
{
var dat = _context.TeamMatches.Include(tm => tm.Matches).ToList();
return View(dat);
}
I get a NullReferenceException when calling TeamMatch.GetMaps() in that View. Specifically in this line, it is supposed to give me an array of unique Maps in all of the IndividualMatches:
list.AddRange(Matches.GroupBy(p => p.MatchMap.ID).Select(g => g.First()));
I assume I somehow need to get "1 level deeper" than just the IndividualMatch that I've included there. How do I accomplish this?
I assume I somehow need to get "1 level deeper" than just the IndividualMatch that I've included there.
That's correct.
How do I accomplish this?
The answer depends on what Entity Framework ae you targeting - EF6 or EF Core, because they use different mechanisms for including multiple levels of related data. That's why it's important to include such information in the question.
Assuming that you use EF Core (based on "my .net core 2.0-mvc app"), Including multiple levels is achieved with chaining Include / ThenInclude expressions:
var dat = _context.TeamMatches
.Include(tm => tm.Matches)
.ThenInclude(m => m.MatchMap) // <--
.ToList();

Fluent Validation, different validation for each item in a list in Asp.NET Core

I´ve been trying to find a way to validate items inside a list, each with different validation rules. I came upon Fluent validation which is a great library but I can´t seem to find a way to do validation for each item individually. I got a faint idea from this similar thread (Validate 2 list using fluent validation), but I´m not sure how to focus it how I want.
So I have this View Model:
public class EditPersonalInfoViewModel
{
public IList<Property> UserPropertyList { get; set; }
}
This contains a list of Active Directory properties. Each represented by this class:
public class Property
{
public string Name { get; set; }
public UserProperties Value { get; set; }
public string input { get; set; }
public bool Unmodifiable { get; set; }
public string Type { get; set; }
}
The point is that, each AD property has different constraints so I want to specify different rules for each property in the list in some way like this:
public class ADPropertiesValidator : AbstractValidator<EditPersonalInfoViewModel>
{
public ADPropertiesValidator()
{
RuleFor(p => p.UserPropetyList).Must((p,n) =>
{
for (int i = 0; i < n.Count; i++)
{
if ((n[i].Name.Equals("sAMAccountName"))
{
RuleFor(n.input ).NotEmpty()....
}
else if(...)
{
//More Rules
}
}
)
}
}
Any ideas how to approach this? thanks in advance.
You are approaching the validation from the wrong perspective. Instead of creating validation conditions inside your collection container class, just create another validator specific for your Property class, and then use that inside your ADPropertiesValidator:
public class ADPropertyValidator : AbstractValidator<Property>
{
public ADPropertyValidator()
{
When(p => p.Name.Equals("sAMAccountName"), () =>
{
RuleFor(p => p.input)
.NotEmpty()
.MyOtherValidationRule();
});
When(p => p.Name.Equals("anotherName"), () =>
{
RuleFor(p => p.input)
.NotEmpty()
.HereItIsAnotherValidationRule();
});
}
}
public class ADPropertiesValidator : AbstractValidator<EditPersonalInfoViewModel>
{
public ADPropertiesValidator()
{
RuleForEach(vm => vm.UserPropertyList)
.SetValidator(new ADPropertyValidator());
}
}

#Html.DropDownList returns null when submitted

I have here a scenario. I want to make an HTTP POST action in the form so here's how I did it.
public class Item
{
public Item()
{
Storages = new HashSet<Storage>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Storage> Storages { get; set; }
-- remove some lines for brevity --
}
public class Storage
{
public int Id { get; set; }
public string Name { get; set; }
--- remove some lines for brevity --
}
So basically, An Item has many Storage And so I created viewmodel.
public class CreateStockViewModel
{
public string Name { get; set; }
public int StorageId { get; set; }
-- remove some lines for brevity --
}
In my Controller. I have this
[HttpGet]
public ActionResult Create()
{
ViewBag.Storages = _storageService.All
.OrderBy(i => i.Name)
.ToSelectList(s => s.Name, s => s.Id);
return View();
}
In my View:
#model Wsfis.Web.ViewModels.ItemViewModels.CreateStockViewModel
#Html.DropDownList("Storages")
Now my problem is, when I submit the form. And have Quick Watch to the model being passed. It is Null or 0
public ActionResult Create(CreateStockViewModel item)
{
// some code
}
In a nutshell,
When I submit the form all fields are being bind except for the #Html.DropDownList. Where did I missed?
Some additional side note:
They say Views should be strongly typed. Then what should I pass in View then? (A sample code would be great. Thanks)
As for the ToSelectList method I copy this code (I hope it's alright)
Any help would be much appreciated. Thanks.
Your form input has a different name to your property so the default model binder doesn't know how to bind your model.
You could pass in a different name to use to the DropDownList helper, however I prefer to use the strongly typed helpers:
#Html.DropDownListFor(m => m.StorageId, ViewBag.Storages as IEnumerable<SelectListItem>)
Try like this:
ViewBag.StorageId = _storageService.All
.OrderBy(i => i.Name)
.ToSelectList(s => s.Name, s => s.Id);
in view:
#Html.DropDownList("StorageId")
it will now post the drop down list selected value in CreateStockViewModel object's StorageId property.

ASP.NET MVC model binding - nested properties

I have a model:
public class DataModel
{
public GridSortOptions SortOptions { get; set; }
public string Term { get; set; }
public int? Page { get; set; }
...
}
public class GridSortOptions
{
public string Column { get; set; }
public SortDirection Direction { get; set; }
}
And a http request: ?Column=LastName&Direction=Descending
That request will not work, i need to update it as follow: ?SortOptions.Column=LastName&SortOptions.Direction=Descending
Is it possible to teach ASP.NET to understand the original request without changing the model?
PS. I know that I could create a custom binder but I feel that there is a much simple way exist...
Thanks in advance!
I have mine structured slightly differently, as I came across the same problem (in MVC1 and still have it now - mvc4) . I have often found that having nested models tend to cause headaches and unnecessary complexity, so I try to flatten my view models as much as possible, just to simplify things, so don't have to write custom model binders, saves a lot of time and code.
My action typically looks method looks like this
//...snip
public ActionResult List(int? page, GridSortOptions sortOptions, string keyword) {
var model = new UserGridViewModel();
IQueryable<User> users = new UserRepository(Session).List();
if (sortOptions.Column != null) {
users = users.OrderBy(sortOptions.Column, sortOptions.Direction);
}
if (keyword != null) {
users = users.Where(x => x.Name.Contains(keyword))
}
model.SortOptions = sortOptions;
//using MvcContrib.Pagination.PaginationHelper here
model.Results = users.AsPagination(page ?? 1, 20);
return View(model);
}
//.....
My view logic is simply:
#using MvcContrib.UI.Grid
#using MvcContrib.UI.Pager
#model UsersGridViewModel
#Html.Grid(Model.Results).Columns(cols => {
cols.For(col => col.Id).Sortable(true);
cols.For(col => col.Name).Sortable(true);
//...etc
}).Sort(Model.SortOptions)
My grid view models are normally like this:
public class UserGridViewModel
{
public IPagination<User> Results { get; set; }
public GridSortOptions SortOptions { get; set; }
}

ASP.NET MVC Design Pattern Best Practice with Services

I have an ASP.NET MVC 3 application.
I have a Model, ViewModel, View, Controller.
I use Ninject as IoC.
My Controller uses a ViewModel to pass data to the View.
I've started to use Services (concrete and interface types) to take information from the ViewModel and query it against the database to manipulate it.
Can I use the same Service to setup the ViewModel? Or is this going against the grain of the design pattern?
I.e. Can I abstract setting up the ViewModel in the Service layer?
Scenario
The scenario is; my Model has lots of references to other Models, so when I setup the ViewModel in the controller it's to verbose, and I feel the Controller is doing too much. So I want to be able to just do something like:
var vm = _serviceProvider.SetupViewModel(Guid model1Id, Guid model2Id, /*etc..*/)
And the SetupViewModel function in the ServiceProvider would look like this:
public MyModelViewModel SetupViewModel(Guid model1Id, Guid model2Id, /*etc...*/)
{
var vm = new MyModelViewModel();
var model1 = _repository.Model1s.FirstOrDefault(x => x.Id.Equals(model1Id));
var model2 = _repository.Model2s.FirstOrDefault(x => x.Id.Equals(model2Id));
// etc....
vm.Model1 = model1;
vm.Model2 = model2;
return vm;
}
By doing this I could also add some null conditions as well, not worrying about making my Controller really really really big!!
I use 1 ViewModel for the Create/Edit actions. I don't reuse the ViewModel elsewhere.
I would let the service layer return a Domain Model and map it to a ViewModel in the controller.
This way you can use a service method with multiple ViewModels, for a desktop and mobile view for example.
You can let AutoMapper do the hard work for you or do it manually, by creating a constructor in the ViewModel which takes the Domain Model.
The domain model:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Telephone { get; set; }
public string Email { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
The ViewModel:
public class CustomerWithOrdersModel
{
public CustomerWithOrdersModel(Customer customer)
{
Id = customer.Id;
FullName = string.Format("{0}, {1}", customer.LastName, customer.FirstName);
Orders = customer.Orders.ToList();
}
public int Id { get; set; }
public string FullName { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
EDIT: AutoMapper example:
The AutoMapper profile containing the mapping from a Customer to a CustomerWithOrdersModel:
public class ViewModelProfile : Profile
{
public override string ProfileName
{
get { return "ViewModel"; }
}
protected override void Configure()
{
CreateMap<Customer, CustomerWithOrdersModel>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Format("{0}, {1}", src.LastName, src.FirstName)))
.ForMember(dest => dest.Orders, opt => opt.MapFrom(src => src.Orders.ToList()));
}
}
Id is mapped by convention.
Extension methods for the ViewModelProfile:
public static class ViewModelProfileExtensions
{
public static CustomerWithOrdersModel ToModel(this Customer customer)
{
return Mapper.Map<CustomerWithOrdersModel>(customer);
}
public static Customer ToEntity(this CustomerWithOrdersModel customerWithOrdersModel)
{
return Mapper.Map<Customer>(customerWithOrdersModel);
}
}
The controller action:
public ActionResult Details(int customerId)
{
Customer customer = _customerService.GetById(customerId);
CustomerWithOrdersModel customerWithOrders = customer.ToModel();
return View(customerWithOrders);
}
If you create a mapping from CustomerWithOrdersModel to Customer, you can use customerWithOrdersModel.ToEntity() to map it back to the Domain Model.
Thats it! You can remove the constructor with the Customer domain model from the ViewModel.
If you have the view models as their own project and handle the mapping and returning of view models in your service layer, I see nothing wrong with that. For separation of concerns you could always have another component that handles the mapping.

Categories