mvc3 composing page and form element dynamically - asp.net-mvc-3

I'm developing an MVC3 application and I have a page (well, a view) that let the users edit document's metainfo (a classic #Html.BeginForm usage). For general documents users will see standard fields to fill up, but through a dropdownlist they will be able to specify the type of the document: this, through an ajax call, will load new fields on the edit-document-form.
Whem the user submit the completed form, at last, the controller should read all the standard fields, plus all the fields loaded as being specific to the type of document selected.
Question is, how can I handle all this extra fields in a controller?
Say that I have Document class and a bunch of other classes extendinf Document, like Contract : Document, Invoice : Document, Complaint : Document and so forth, each having specific property (and this fields loaded on the form), how do I write the action in the controller?
I thought to use something like (I'll omitt all the conversions, validations, etc, for brevity)
[HttpPost]
public ActionResult Save(dynamic doc)
{
int docType = doc.type;
switch (docType)
{
case 1:
var invoice = new Invoice(doc);
invoice.amount = Request.Form["amount_field"];
invoice.code = Request.Form["code_field"];
//and so forth for every specific property of Invoice
Repository.Save(invoice);
break;
case 2:
var contract = new Contract(doc);
contract.fromDate = Request.Form["fromDate_field"];
contract.toDate = Request.Form["toDate_field"];
//and so forth for every specific property of Contract
Repository.Save(contract);
break;
..... // and so forth for any document types
default:
break;
}
}
But it seems a very dirty approach to me. Do you have a better idea on how to achive this? Maybe there's a pattern that I don't know nothing about to approach this kind of scenario.
Update
A second idea comes to my mind. After commenting Rob Kent's answer, I thought I could take a different approach, having just one class Document with a property like
public IEnumerable<Field> Tipologie { get; set; }
where
public class Field
{
public int IdField { get; set; }
public String Label { get; set; }
public String Value { get; set; }
public FieldType ValueType { get; set; }
public List<String> PossibleValues { get; set; } // needed for ENUMERATION type
}
public enum FieldType
{
STRING, INT, DECIMAL, DATE, ENUMERATION
}
Is this a better approach? In this case I can have just an action method like
[HttpPost]
public ActionResult Save(Document doc)
But shoud I create the fields in the view in order to make the MVC engine do the binding back to the model?
Given that the class inheriting from Document in the first approach will probably be generated at run-time, would you prefer this second approach?

To keep it all hard-typed on the server, you could use an abstract base type with a custom binder. See my answer here to see how this works: MVC generic ViewModel
The idea is that every time they load a new set of fields, you change the BindingType form variable to the instantiated type of the handler. The custom binder is responsible for creating the correct type on submission and you can then evaluate that in your action, eg:
if (model is Contract) ...
I'm not sure if you will be able to set up different actions each with a different signature, eg,:
public ActionResult Save(Contract contract) ...
public ActionResult Save(Invoice invoice) ...
Pretty sure that won't work because Mvc will have already decided which method to call, or maybe it will firstly see what type it gets back and then decides.
In my linked example, I am checking for overridden base members but if that is not an issue for you, you just need to create the correct type.

Related

Is there any way to pass a whole model via html.actionlink in ASP.NET MVC 3?

How do I pass a whole model via html.actionlink or using any other method except form submission? Is there any way or tips for it?
Though it's not advisable in complex cases, you can still do that!
public class QueryViewModel
{
public string Search { get; set; }
public string Category { get; set; }
public int Page { get; set; }
}
// just for testing
#{
var queryViewModel = new QueryViewModel
{
Search = "routing",
Category = "mvc",
Page = 23
};
}
#Html.ActionLink("Looking for something", "SearchAction", "SearchController"
queryViewModel, null);
This will generate an action link with href like this,
/SearchController/SearchAction?Search=routing&Category=mvc&Page=23
Here will be your action,
public ViewResult SearchAction(QueryViewModel query)
{
...
}
No, you cannot pass entire complex objects with links or forms. You have a couple of possible approaches that you could take:
Include each individual property of the object as query string parameters (or input fields if you are using a form) so that the default model binder is able to reconstruct the object back in the controller action
Pass only an id as query string parameter (or input field if you are using a form) and have the controller action use this id to retrieve the actual object from some data store
Use session
You could use javascript to detect a click on the link, serialize the form (or whatever data you want to pass) and append it to your request parameters. This should achieve what you're looking to achieve...

MVC3 validate only one entity in ViewModel

I have an mvc3 create page using a View Model with 2 entities
like
class ViewModel1{
public User user{get;set;}
public Company company{get;set;}
}
where User and Company are EF4 entities(tables). I need to use a single page to create both(related) tables. Now the Company entity is optional under some conditions and I use jQuery to hide the corresponding section in the view.
However since company has required fields , the post back create function has ModelState.Valid as false.
What I want to do is if the Company section is hidden, I would like to skip validating the Company entity in ViewModel in Server( I avoid validation of hidden elements in Client).
Maybe there is a better and more proper approach to this?
What you have shown is not a view model. You call it a view model but it isn't because it is referencing your EF domain entities.
A more realistic view model would look like this:
class ViewModel1
{
public UserViewModel User { get;set; }
public CompanyViewModel Company { get; set; }
}
or even flattened out and containing only the properties that your view needs:
class ViewModel1
{
public int UserId { get;set; }
[Required]
public string FullUserName { get;set; }
[Required]
public string CompanyName { get; set; }
}
Now depending on your specific requirements about view model validation your UserViewModel and CompanyViewModel classes will be specifically designed for them.
Instead of putting the entities directly in the view model, put the properties for the entities in the view model and map between the view model and the actual entity objects on the server. That way you can control what properties are required for your view. Create some custom validation rules to validate that the required company properties are there when some company information is required. To do this on the server, you can have your view model implement IValidatableObject and implement the logic in the Validate method. On the client you can add rules to the jQuery validate plugin for the "required if" properties.
public class UserCreationViewModel : IValidatableObject
{
[Required]
public string Username { get; set; }
[Required]
public string FirstName { get; set; }
...
public string CompanyName { get; set; }
public string CompanyEmail { get; set; }
public IEnumerable<ValidationResult> Validate( ValidationContext context )
{
if (!string.IsNullOrEmpty(CompanyName) && string.IsNullOrEmpty(CompanyEmail))
{
return yield new ValidationResult("Company Email is required if you specify a company", new [] { "CompanyEmail" });
}
}
}
I'm not sure what I would do on the client-side. You have a choice of either adding specific rules to the validate plugin directly, but it might be hard to make it look exactly the same as using the unobtrusive validation that MVC adds. Alternatively, you could look at adding/removing the unobtrusive attributes from the "required if" elements using jQuery depending on the state of the elements that trigger their requirement. I suggest trying both ways -- look at the docs for the validate plugin to see how to add custom rules and examine the code emitted by the MVC framework for the unobtrusive validate to see what you would need to add to make that work.
Another possibility would be including/removing a partial view with the company properties in the from based on whether the company information is required or not. That is, type in a company name and use AJAX to grab the inputs required for the company and add them to the form. If the company name is deleted, delete the elements. Leave the server-side validation the way it is, but in the partial view mimic the HTML that the framework would add in for unobtrusive validation. This is sort of the best of both worlds as the jQuery code is much simpler and you get consistent validation, too.
There are many ways you can achieve,
1) more commonly donot use [Required] attribute on Company object, but have proper validation for parameters inside Company object.
In this case if Company object is null still validation will pass, but if Company object isnot null it will validate each properties.
2) If validation involves some complex business logic, then go for Self Validating Model. (inherit from IValiddatableObject, and override Validate(...).
3) By code, in the controller.
if(model.company == null)
this.ModelState.Keys.Where(k => k.Contains("company")).ToList().ForEach(k => this.ModelState.Remove(k));
first two are best approved approaches, third is just another way to achieve your functionalities

AutoMapper on complicated Views (multiple looping)

Earlier today, a helpful person (here on Stack Overflow) pointed me towards AutoMapper, I checked it out, and I liked it a lot! Now however I am a little stuck.
In my Code First MVC3 Application, on my [Home/Index] I need to display the following information from my Entities:
List of Posts [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]
List of Tags [int Id, string Name]
List of All Authors that exist on my Database [ string UrlFriendlyName ]
So far I have managed only point 1 in the list by doing the following for my Index ViewModel:
public class IndexVM
{
public int Id { get; set; }
public string Body { get; set; }
public int Likes { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
}
And on the Home Controller, Index ActionMethod I have:
public ActionResult Index()
{
var Posts = postsRepository.Posts.ToList();
Mapper.CreateMap<Post, IndexVM>();
var IndexModel = Mapper.Map<List<Post>, List<IndexVM>>(Posts);
return View(IndexModel);
}
Finally on my View I have it strongly typed to:
#model IEnumerable<BlogWeb.ViewModels.IndexVM>
And I am passing each Item in the IndexVM IEnumberable to a Partial View via:
#foreach (var item in Model)
{
#Html.Partial("_PostDetails", item)
}
My question is, how can I also achieve point 2 and 3, whilst not breaking what I've achieved in point 1.
I tried putting the stuff I currently have for IndexVM into a SubClass, and having a List Property on the Parent class, but it didn't work.
From the ASP.NET MVC2 In Action Book:
Some screens are more complex than a single table. They may feature
multiple tables and additional fields of other data: images, headings,
subtotals, graphs, charts, and a million other things that complicate
a view. The presentation model solution scales to handle them all.
Developers can confidently maintain even the gnarliest screens as long
as the presentation model is designed well. If a screen does contain
multiple complex elements, a presentation model can be a wrapper,
composing them all and relieving the markup file of much complexity. A
good presentation model doesn’t hide this complexity—it represents it
accurately and as simply as possible, and it separates the data on a
screen from the display.
Make a ViewModel that represents your screen. Then build it up and pass it to the View. This book is great and talks about using a presentation model. With AutoMapper, think about how you would accomplish your mapping without it, then make use of it. AutoMapper isn't going to do anything magic, it eliminates keyboard slapping.
AutoMapper aside, take your list of requirments:
List of Posts [ int Id, string Body, int Likes, string p.User.FirstName, string p.User.LastName ]
List of Tags [int Id, string Name]
List of All Authors that exist on my Database [ string
UrlFriendlyName ]
and assuming you have these Model entites: Post, Tag, Author
Personally I don't like passing Model entities to my presentation in MVC or MVVM but that's me. Say we follow that here and create PostDisplay, TagDisplay, and AuthorDisplay.
Based on the View's requirements the ViewModel will look like this:
Public class IndexVM
{
Public List<PostDisplay> Posts {get; set;}
Public List<TagDisplay> Tags {get; set;}
Public List<AuthorDisplay> Authors {get; set;}
}
In this case the way the View is composed will require you to build it up:
public ActionResult Index()
{
var posts = postsRepository.Posts.ToList();
var tags = postsRepository.Tags.ToList();
var authors = postsRepository.Authors.ToList();
Mapper.CreateMap<Post, PostDisplay>();
Mapper.CreateMap<Tag, TagDisplay>();
Mapper.CreateMap<Author, AuthorDisplay>();
private var IndexVM = new IndexVM
{
Posts = Mapper.Map<List<Post>, List<PostDisplay>>(posts),
Tags = Mapper.Map<List<Tag>, List<TagDisplay>>(tags),
Authors = Mapper.Map<List<Author>, List<AuthorDisplay>>(authors)
};
return View(IndexVM);
}
So, what you end up with is a ViewModel to pass to your view that represents exactly what you want to display and isn't tightly coupled to your Domain Model. I can't think of a way to have AutoMapper map three separate result lists into one object.
To clarify, AutoMapper will map child collections so a structure like:
public class OrderItemDto{}
public class OrderDto
{
public List<OrderItemDto> OrderItems { get; set; }
}
will map to:
public class OrderItem{}
public class Order
{
public List<OrderItem> OrderItems { get; set; }
}
As long as you tell it how to map the types: OrderDto -> Order and OrderItemDto -> OrderItem.
As an alternative to including all of your lists of entities on a single viewmodel, you could use #Html.Action. Then, in your screen view:
#Html.Action("Index", "Posts")
#Html.Action("Index", "Tags")
#Html.Action("Index", "Authors")
This way, your Index / Screen view & model don't need to know about the other viewmodels. The partials are delivered by separate child action methods on separate controllers.
All of the automapper stuff still applies, but you would still map your entities to viewmodels individually. The difference is, instead of doing the mapping in HomeController.Index(), you would do it in PostsController.Index(), TagsController.Index(), and AuthorsController.Index().
Response to comment 1
public class IndexVM
{
// need not implement anything for Posts, Tags, or Authors
}
Then, implement 3 different methods on 3 different controllers. Here is one example for the PostsController. Follow the same pattern for TagsController and AuthorsController
// on PostsController
public PartialViewResult Index()
{
var posts = postsRepository.Posts.ToList();
// as mentioned, should do this in bootstrapper, not action method
Mapper.CreateMap<Post, PostModel>();
// automapper2 doesn't need source type in generic args
var postModels = Mapper.Map<List<PostModel>>(posts);
return PartialView(postModels);
}
You will have to create a corresponding partial view for this, strongly-typed as #model IEnumerable<BlogWeb.ViewModels.PostModel>. In that view, put the HTML that renders the Posts UI (move from your HomeController.Index view).
On your HomeController, just do this:
public ActionResult Index()
{
return View(new IndexVM);
}
Keep your view strongly-typed on the IndexVM
#model IEnumerable<BlogWeb.ViewModels.IndexVM>
... and then get the Posts, Tags, and Authors like so:
#Html.Action("Index", "Posts")
Response to comment 2
Bootstrapping... your Mapper.CreateMap configurations only have to happen once per app domain. This means you should do all of your CreateMap calls from Application_Start. Putting them in the controller code just creates unnecessary overhead. Sure, the maps need to be created - but not during each request.
This also helps with unit testing. If you put all of your Mapper.CreateMap calls into a single static method, you can call that method from a unit test method as well as from Global.asax Application_Start. Then in the unit test, one method can test that your CreateMap calls are set up correctly:
AutoMapperBootStrapper.CreateAllMaps();
Mapper.AssertConfigurationIsValid();

TryUpdateModel for a model containing a list removes information from model

I have a big object that I resorted to serializing using #Html.Serialize():
[Serializable]
public class ModelB
{
public List<ModelA> ListOfModelA { get; set; }
// more stuff
}
This object contains a list of objects from a class that contains several properties. Some of them I include them in my view, while other I do not even bother to put them as hidden fields, as I have them in my serialized model.
[Serializable]
public class ModelA
{
public string StringA { get; set; }
public string StringB { get; set; }
// more stuff
public string HiddenStringA { get; set; }
public string HiddenStringB { get; set; }
// more stuff
}
Now, when I post back the form with my changes I reconstract my model and then I update it using the dictionary of values obtained from the form.
[HttpPost]
public ActionResult Edit([Deserialize] ModelTwo model,
FormCollection form)
{
TryUpdateModel(model, form.ToValueProvider());
// more stuff
}
I step in my code and just before I do the update, I see that my deserialized model contains a list ListOfModelA that in turn contains all the elements that should be there, and within them I can see all the HiddenStringA and HiddenStringB properties. Then I peek inside the form and I see a dictionary with keys like these:
ListOfModelA[0].StringA
ListOfModelA[0].StringB
ListOfModelA[1].StringB
ListOfModelA[1].StringB
while there are NO keys for the rest of the properties like this one:
ListOfModelA[0].HiddenStringA
Next, I move one step further and I let the code do the TryUpdateModel. Now, looking inside the ListOfModelA property, all the elements have been replaced with new ones that have all the hidden values null. It is as if the update reconstructed whole elements (with the limited information it had), rather than updating only the properties for which it had information.
Is this the expected behaviour? Is there a way to keep my model, and update only the properties that have keys in the dictionary?
Thanks,
Panos
My problem is more complex than the one described above, but the solultion that I found for the specific design is along the following lines. I serialize the part of the model that is a list, and bind the whole model through an argument of the action. This way I avoid using the TryUpdateModel, although this is not what causes me the trouble. Then, I inject the non-null values into the deserialized model from the model created from the form, and lastly I point the corresponding part of the latter to the former.
[HttpPost]
public ActionResult Edit(ModelTwo model,
[Deserialize] List<ModelA> serializedList)
{
serializedList.InjectFrom<NonNullLoopValueInjection>(model.ListOfModelA);
model.ListOfModelA = serializedList;
// more stuff
}
For the injection, I use Value Injecter where NonNullLoopValueInection is defined below
public class NonNullLoopValueInjection : LoopValueInjection
{
protected override bool AllowSetValue(object value)
{
return value != null;
}
}
Maybe not the best design, but it is working, and it might lead me to something better. Any feedback is more than welcome.

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.

Resources