ASP.NET MVC Dynamic Views - asp.net-mvc

I Posted the background to this question a few days ago.. but the answer is yet incomplete, so re-Posting with a summary.
I'm developing a MVC 3 Razor Web App, in which details of multiple categories of objects are stored. (Vehicles, Houses, Instruments etc) . All the objects share some common data (Title, Description etc) And some details which are specific to the category in which it belongs to. The Category list is expected to grow and in view of reducing maintainability we hope to reuse the same Add Object wizard . The wizard is based on the following implementation.
http://afana.me/post/create-wizard-in-aspnet-mvc-3.aspx
In the Multiple step wizard process , the final step allows the user to enter the category specific details (Model, Make, VIN etc for a Vehicle). The view page is bound to the "AssetView" Model, which is defined as follows
public class AssetView
{
[Required]
public string Title { get; set; }
[Required]
public string Description { get; set;}
public SpecificAsset AssetDetails { get; set; }
}
the AssetDetails property will be dynamically changed at runtime according to the category type that is selected at a previous stage. the view looks like this
#model AssetView
....
<div class="wizard-step">
...
</div>
<div class="wizard-step">
...
</div>
<div class="wizard-step">
#Html.EditorFor(model => model.AssetDetails)
</div>
in the controller, based on category selection . i intialize assetdetails as follows
_thisAsset.AssetDetails = new MotorAsset();
I then override the views with type specific Editor templates.
The wizard works fine, but At the Save step in the controller the "AssetDetails" property has no values and it is shown to be of type "SpecificAsset" which is the base type. i'm unable to cast it to the specific type as well. However the formsCollection shows all values, the "AssetDetails" specific properties like "Transmission", "Make" are in the forms collection prefixed by "AssetDetails_" .
Keeping in mind that i need to handle over 20 types of specific categories (Motor, House etc) at the controller save step, how can i do this without some kind of hack?

If you know the type of the AssetDetails you could do it like this:
Take the AssetView as parameter to
the action(binds the common
properties)
Create a new instance of the
specific AssetDetails(for example
CarDetails)
Use one of the overloads of
TryUpdateModel to bind the values.
There are some overloads that let
you specify prefix so it should
always work
Add this object to the
AssetView.SpecificAsset
You need some way to know the specific type. But I guess you would have a variable that tracks the type so you could save it properly later anyway. If you don't it would be easy to add. Remember that it needs to be AssetView as AssetDetails are not being bound though.
If you need validation there is a TryValidateModel you could try too.

As I understood, number of models will grow and you want to reuse action and view for all models. One option I see is to make a custom ModelBinder. You would examine FormCollection and than create a specific asset model.
The problem with your solution is that SpecificAsset has no properties, so default model binder doesn't bind any of fields to it.

Related

MVC5 - Dynamically choose which properties are displayed in an form

Let's say I have two users who are accessing a form. One is an admin user and one is a normal user. And only the admin can view and edit AdminOnlyField Then let's say I have the following class:
public class Car
{
public string Make {get;set;}
public string Model {get;set;}
public string AdminOnlyField {get;set;}
}
I'm not able to figure out how to conditionally make the AdminOnlyField visible and editable using #html.BeginForm(), I know you can use properties from ViewBag but in this case all the information I need is coming back from a database so I don't think I can utilize ViewBag.
Is this something MVC can accomplish or do I need to explore Angular?
The easiest way is to insert if(User.IsInRole(stringRole))
Yet if you want to hide this mechanism you can make an EditorTemplate.
The file has to be in location Views/Shared/EditorTemplates/EditFormTemplate.cshtml
For AdminOnlyField you can hide this functionality by template.
#if(User.IsInRole("Admin")
{
<div>
<label>AdminOnlyField: </label>
#Html.EditorFor(model => Model.AdminOnlyField)
</div>
}
Usage:
#Html.EditorForModel("YourCustomTemplateName")
If you need more information:
There is more information

What is the right design pattern for custom template types in ASP.NET MVC?

Here's my situation: I've got a number of specialized object types in my application, and I'm following the standard convention for displaying them with custom templates in the /Shared/DisplayTemplates folder, and editing them with templates in /Shared/EditorTemplates. But I also want to be able to display a custom filter template for each type, so I'd like to add a /Shared/FilterTemplates folder, and implement my own #Html.FilterFor method, so that showing a Filter template is exactly like showing a Display or Editor template.
Does this seem like the best way to handle this situation, or is there a more correct/elegant way to do this in MVC? Thanks in advance.
I'm always using EditorTemplates when data is sent back to server. I assume the user can submit the filter to the server to perform the actual filtering.
When creating filters I prefer to create a model for the filter like:
public class UserListFilterModel
{
public string Username { get; set; }
public bool IsEnabled { get; set; }
}
The view for UserListFilterModel goes into EditorTemplates/UserListFilterModel.ascx.
And then add it as a property on my view model for the page.
public class MyPageViewModel
{
public UserListFilterModel Filter { get; set; }
}
Then I add the filter model to the model for the page and displays it like this:
<%= Html.EditorFor(x => x.Filter)%>
You are probably wrapping the filter in a form to allow the user to submit the values so I think it belongs in EditorTemplates. The users is in fact editing the filter model.
(If you really want to separate them ing you could use the UIHintAttribute but I wouldn't)
Edit: I added some sample code.
I think you misunderstand how Templates work. Templates do not make sense in the context you are describing.
Templates work on a SINGLE data item (although that data item can contain multiple data items, which in turn have their own templates).
The concept of a Filter is to control multiple data items, thus they do not map well to a template.
What you could do is create a DisplayTemplate for your collection class that adds filtering, thus no need to create a custom type of template. Just use DisplayTemplates.

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

Defining view models for MVC / MVP

Short question - how do you define your view models?
Here are some of the options:
Pass the actual model into the view.
Create a view model with a reference to the model (like Model.Product)
Create a view model with the properties needed from the model, and set those from the model.
Probably a lot more.
All with their own advantages and disadvantages.
What is your experience - good and bad? And do you use the same model for GET/POST?
Thanks for your input!
Basically - it's all about separating responsibilities.
More you separate them - more verbose, complex but easier to understand it gets.
Model:
public class foo{
string Name{get;set}
Bar Bar {get;set;}
string SomethingThatIsUneccessaryInViews {get;set;}
}
public class bar{
string Name {get;set;}
}
public class fizz{
string Name{get;set;}
}
Presenter (i admit - still haven't got idea of MVP completely):
public someSpecificViewPresenter{
fizz fizz{get;set;}
foo foo{get;set;}
necessaryThingsForWhatever[] necessaryThingsForWhatever{get;set;}
public void someLogicIfNeeded(){...}
}
magic object2object mapping & flattening, viewmodel modelmetadata configuration goes here...
ViewModel (NB=>POCOS with container props only. No logic should go here.):
public class fooViewModel{
string Name {get;set;}
string BarName {get;set;}
}
public class fizzViewModel{
string Name {get;set;}
}
public class someSpecificView{
fooViewModel foo {get;set;}
fizzViewModel fizz {get;set;}
whateverViewModel whatever {get;set;}
}
and here goes "das happy ending"...
<use viewdata="someSpecificView m" />
<p>
Our foo:<br/>
${Html.DisplayFor(x=>x.foo)}
</p>
<p>
Our fizz:<br/>
${Html.DisplayFor(x=>x.fizz)}
</p>
${Html.UberPaging(m.whatever.Paging)}
And yes, i use same model for GET/POST. See this for more why/ifs.
But lately - I'm looking for other solutions. CQRS buzz catch my eye.
In my projects, it's a mix really.
If I want to display a form with details of Customer X, I just pass a DAL Customer object to my view. It's really no use to create a seperate ViewModel for it, map all its properties, and then display them. It's a waste of time imho.
Sometimes though, models are a bit more complex. They're the result of multiple queries, have some added data to them, so in these cases, I create a custom ViewModel, and add the necessary data from my model to it. In your case, it would be option 2, or sometimes 3. I prefer that over passing my model and having to add an additional 10 items in my ViewData.
I grabbed the T4 templates from SubSonic 3. These were modified and I added some new ones. I can run one of them and it generates 3 separate view models for each table. Then I can modify as needed.
Why three?
FormModel - contains on the data necessary for displaying in a form for editing or creation. Foreign keys get converted to SelectLists. DateTime fields get split into date and time components.
PostModel - this is the object returned from the Form Post. DropDownLists are posted as Int or equivalent type. Only the necessary members are in the model.
DisplayModel - used for non-editing display of the data.
I always generated these in a subfolder named Generated. As I hand tweek them I move them to the Models folder. It doesn't completely automate the process, but it generates a lot of code I would otherwise generate by hand.

How to create a MVC 2 DisplayTemplate for a field whose display format is dependent on another field?

If I have a property whose display format is dependent on the value of another property in the view model how do I create a display template for it?
The combination of field1's display being dependent on field2's value will be used throughout the app and I would like to encapsulate this in a MVC 2 display template.
To be more specific, I've already create a display template (Social.ascx) for custom data type Social that masks a social security number for display. For instance, XXX-XX-1234.
[DataType("Social")]
public string SocialSecurityNumber { get; set; }
All employees also have an employeeID. Certain companies use the employee's social security number as either the whole employee id or as part of it. I need to also mask the employeeID if it contains the social. I'd like to create another display template (EmpID.ascx) to perform this task.
[DataType("EmpID")]
public string EmployeeID { get; set; }
The problem is that I don't know how to get both properties in the "EmpID" template to be able to perform the comparison.
Thanks for the help.
This might not directly answer your question but I'm wondering why the Employee ID is only sometimes marked out. I know there are legal requirements to doing so for the social but the employee ID is (or should be) somewhat sensitive as well. I would think it would be better to default to marking out both unless the logged in user had whatever privileges made them fully readable.
If you can do this that would probably simplify your logic/design somewhat.
Cant you create a custom ViewModel class containg both SocialSecurityNumber and EmployeeID and create a custom editor template for that class?

Resources