How to optionally load expensive viewmodels for partial views? - devexpress

DevExpress's PageControl loads the content for the selected tab page on demand:
#Model IndexViewModel
#Html.DevExpress().PageControl(settings =>
{
settings.TabPages.Add("Dashboard").SetContent(() =>
{
Html.RenderPartial("_Dashboard", ???);
});
settings.TabPages.Add("Review photos (3)").SetContent(() =>
{
Html.RenderPartial("_ReviewPhotos", ???);
});
...
}
However this makes it difficult to pass the view models into each partial view as they each have their own requirements.
Options:
1) All views & partials share the same view model, simply pass Model through - unfortunately means the controller must load all content for all pages on every request which is too inefficient
2) Have separate view models nested, pass Model.[Child]ViewModel through - same inefficiencies as (1)
3) Have loading functions on main view's model eg.:
settings.TabPages.Add("Dashboard").SetContent(() =>
{
Html.RenderPartial("_Dashboard", Model.CreateDashboardViewModel());
});
settings.TabPages.Add("Review photos (3)").SetContent(() =>
{
Html.RenderPartial("_ReviewPhotos", Model.CreateReviewPhotosViewModel());
});
...
public class IndexViewModel
{
public Func<DashboardPhotosViewModel> CreateDashboardPhotosViewModel { get; set; }
public Func<ReviewPhotosViewModel> CreateReviewPhotosViewModel { get; set; }
}
...
var viewModel = new IndexViewModel
{
CreateDashboardPhotosViewModel = () =>
{
//Load dashboard specific elements
}
...
}
4) Don't render partials directly - do through Html.RenderAction("..") instead allowing viewmodels to be created within the action method. (This is a possibility but may cause other issues, so I'm interested to hear alternatives)
I like (3) but it seems like an anti pattern to put functions on a view model.
I also require the ability to create these child view models individually for AJAX callbacks etc.
Is there a best practice way of doing this?

For the record, we went with option (4) - call the Action on the controller, which can create its own viewmodel.

Related

How to display view model validation for a view that has multiple forms on it?

I'm trying to better understand how to properly structure my ASP.NET MVC code to handle a situation where a single view contains multiple forms. I feel that it makes sense to submit the forms to their own action methods, so that each form can benefit from its own view model parameter binding and validation, and to avoid putting all form parameters into 1 larger, monolithic view model.
I'm trying to code this pattern, but I can't seem to tie the loose ends together.
I've written some example action methods below, along with example view model classes, that I think demonstrate what I'm trying to achieve. Lets say that I've got an Item Detail action method and view. On this Detail view, I've got two forms - one that creates a new Comment and another that creates a new Note. Both Comment and Note forms POST to their own action methods - DetailNewComment and DetailNewNote.
On success, these POST handler action methods work just fine. On an invalid model state though, I return View(model) so that I can display the issues on the original Detail view. This tries to render a view named Brief though, instead of Detail. If I use the overloaded View call that allows me to specify which view to render, then now I have issues with the different view model classes that I'm using. The specific view model classes now no longer work with the original DetailViewModel.
I get the feeling that I'm doing this completely wrong. How am I supposed to be handling this scenario with multiple forms? Thanks!
public ActionResult Detail(int id)
{
var model = new ItemDetailViewModel
{
Item = ItemRepository.Get(id)
};
return View(model);
}
[HttpPost]
public ActionResult DetailNewComment(int id, ItemDetailNewCommentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var comment = CommentRepository.Insert(new Comment
{
Text = model.Text
});
return RedirecToAction("Detail", new { id = id; });
}
[HttpPost]
public ActionResult DetailNewNote(int id, ItemDetailNewNoteViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var note = NoteRepository.Insert(new Note
{
Text = model.Text
});
return RedirectToAction("Detail", new { id = id; });
}
... with view models something like ...
public class ItemDetailViewModel
{
public Item Item { get; set; }
}
public class ItemDetailNewCommentViewModel
{
public string Text { get; set; }
}
public class ItemDetailNewNoteViewModel
{
public string Text { get; set; }
}
For your case I'd recommend to have a master model for example your
ItemDetailViewModel class to which you'll add a property for each sub-model
public class ItemDetailViewModel
{
public Item Item { get; set; }
public ItemDetailNewCommentViewModel NewCommentModel {get;set;}
public ItemDetailNewNoteViewModel NoteModel {get;set;}
}
Your Detail view will be the master view and the other two will be partial views.
Master view will receive an instance of ItemDetailViewModel as model and inside view you will render your partials by passing Model.NewCommentModel and Model.NoteModel as their corresponding models. For being able to use separate actions for each form, instead of regular forms you can use ajax forms, thus you will send to the server only relevant information without altering the rest of the master view.
The chief problem here is what happens when the user messes up and their post doesn't pass validation server-side. If you choose to take them to a page where just the one form is presented, then you can post to a different action, but if you want both forms re-displayed, then they both should point to the same action.
Really, you just have to make a choice. I've seen sites handle it both ways. Personally, I prefer to re-display the original form, which means handling both forms in the same action. It can lead to bloat, but you can factor out a lot of logic from the action such that you end up with mostly just a branch depending on which form was submitted.

Dynamic role providing in asp.net mvc (Roles are not fixed It is keep updating)

I am aware simple role provider in which if i need to restrict particular action i have to simply write Authorize(Roles = "Admin") or if i need to restrict particular part of view i nned to write #if(User.IsInRole("Admin")).
But my question is that what if my roles are not fixed and it is stored in database and my super admin can able to edit and delete them.
My requirement is that superadmin can add,update,delete roles and also create different users and maintain the roles of those users.
I have done lot of googling and found something as follows
[AttributeUsage (AttributeTargets.Method|AttributeTargets.Class,Inherited = true,AllowMultiple=true) ]
public class CustomRole : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase context)
{
Respository db = new Respository();
if (db.UserMasters.Where(x => x.user_name == context.User.Identity.Name).Count() > 0)
{
return true;
}
else { return false; }
}
}
Here i can use this code to authorize action method as follows
[CustomRole]
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
Here my this action method is protected but what if i want to protect some part of view by this custom method. How to use this functionality to achieve functionality as User.IsInRole("Admin")?
your requirement will get in 3 steps
1- Create all default roles, store it in database.i.e- roleid,rolename
2- When creating new user map userid with roleid.
3- also make one table for all permission which you have to give.
4- make seperate ui for admin to change the roles of each user.
database will be like below image.
and ui will be like this.
try this yousrelf..
Fully answering your question might be out of scope for StackOverflow, since it would basically require writing most of an application for you, but here's the general idea.
Write a helper class similar to this:
public class ModuleHelper
{
public static bool UserCanAccessModule(string moduleIdentifier)
{
bool canAccess = false;
/*
Call into your service with current User.Name and module identifier (integer, GUID, whatever).
Return result of whether user has the required role for the specified module
*/
try
{
canAccess = service.CanUserAccessModule(User.Identity.Name, moduleIdentifier);
}
catch
{
// catching all exceptions, since this is a UI helper
}
return canAccess;
}
// etcetera...
}
I'd suggest wrapping it in the root namespace of your application; otherwise, add a reference to this class's namespace in the system.web.webPages.razor section of the web.config in the Views folder. Then, you can do something like:
<div class="col-sm-3 col-md-2 sidebar">
#if (ModuleHelper.UserCanAccessModule("moduleXYZ"))
{
#Html.Action("moduleXYZ")
}
</div>
This obviously assumes a lot, but the idea isn't new or all that complicated in practice. The logic of the service is relatively simple:
Look up the user
Look up the "action" or "module"
Look for intersection (if any) between the roles assigned to each.
No intersection means user doesn't have the required role.
Tieson T. has a great answer to your question already, so what I'll provide here is an alternative method if you wanted to keep all of your authorization steps all in controllers.
Consider separating the different aspects (or restricted parts) of your main view into a partial view (or views) that perform the restricted functionality. Then, instead of using: #Html.RenderPartial("ViewName", Model) you can set up your partials to be returned from controller actions decorated with the ChildActionOnly Attribute by using the RenderAction Html Helper.
For example:
<div class="col-sm-3 col-md-2 sidebar">
#Html.RenderAction("RestrictedContent")
</div>
Then in your controller class
public class RestrictedController : Controller {
public RestrictedController() : base() {
}
[ChildActionOnly()]
[CustomRole()]
public ActionResult RestrictedContent() {
return PartialView("RestrictedPartial");
} // end action RestrictedContent
} // end class
The only consideration with this approach will be in your custom attribute to interrogate the the IsChildAction property to avoid rendering a redirect or whatever your attribute does in the case the user is not authorized since you'll probably want to just not render anything.
For Example (in your custom attribute class):
public override void OnAuthorization(AuthorizationContext filterContext) {
if(filterContext.IsChildAction) {
filterContext.Result = new EmptyResult(); // return an empty result instead of performing a redirect.
} else {
base.OnAuthorization(filterContext); // continue with custom authorization if it is not a child action
} // end if/else
} // end method OnAuthorization
Phil Haack has an article describing the usage of the RenderAction method here: http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx/
Also, see here for an interesting discussion on the differences between Action and RenderAction. The difference between Html.Action and Html.RenderAction

How to add custom ClientValidationRules (unobtrusive validation) for a complex type on a model?

Say I have a custom validation attribute ValidateFooIsCompatibleWith model like so:
public class FooPart
{
public string Foo { get; set; }
public string Eey { get; set; }
}
public class FooableViewModel
{
public FooPart Foo1 { get; set; }
[ValidateFooIsCompatibleWith("Foo1")]
public FooPart Foo2 { get; set; }
}
Let's say I also have custom EditorTemplates defined for FooPart:
#Html.TextBoxFor(m => m.Foo)
#Html.TextBoxFor(m => m.Eey)
And thus my view is essentially:
#Html.EditorFor(m => m.Foo1)
#Html.EditorFor(m => m.Foo2)
Server side, the validation works fine. However, no matter what I try, I can't get the rendered html to add the rule.
If I implement IClientValidatable, it turns out that GetClientValidationRules() never gets called. (I have successfully used IClientValidatable with "simple" fields before).
I also tried registering my own custom adapter by inheriting from DataAnnotationsModelValidator<TAttribute> and registering it in the global.asax with DataAnnotationsModelValidatorProvider.RegisterAdapter(...) That approach too fails to call GetClientValidationRules().
** Update **
If a add both a custom ModelMetadataProvider and a custom ModelValidatorProvider so that I can set breakpoints, I notice an interesting bit of behavior:
a request is made to the ModelMetadataProvider for metadata with a ContainerType of FooableViewModel and a ModelType of FooPart. However, no corresponding request is made to the ModelValidatorProvider, so I can't insert my custom client validation rules there.
requests are made to the ModelValidatorProvider with a ContainerType of FooPart and a ModelType of string for both the Foo and Eey properties. But at this level, I don't know the attributes applied to the FooPart property.
How can I get the MVC framework to register my custom client validation rules for complex types?
I found a solution:
First, Create a custom model metadata provider (see https://stackoverflow.com/a/20983571/24954) that checks the attributes on the complex type, and stores a client validatable rule factory in the AdditionalValues collection, e.g. in the CreateMetadataProtoype override of the CachedDataAnnotationsModelMetadataProvider
var ruleFactories = new List<Func<ModelMetadata, ControllerContext, IEnumerable<ModelClientValidationRules>>>();
...
var clientValidatable = (IClientValidatable)attribute;
ruleFactories.Add(clientValidatable.GetClientValidationRules);
...
result.AdditionalValues.Add("mycachekey", ruleFactories);
Next, register this as the default metadata provider in the global.asax
protected void Application_Start()
{
ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
....
}
Then I created an html helper that would process the modelmetata and create/merge the "data-val*" html attributes from each of AdditionalValues collection.
public static IDictionary<string, Object> MergeHtmlAttributes<TModel>(this HtmlHelper<TModel>, object htmlAttributes = null)
{
var attributesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
//ensure data dictionary has unobtrusive validation enabled for the element
attributesDictionary.Add("data-val", "true");
//loop through all the rule factories, and execute each factory to get all the rules
var rules = ruleFactory(helper.Html.ViewData.ModelMetadata, helper.Html.ViewContext);
//loop through and execute all rules in the ruleFactory collection in the AdditionalValues
//and add the data-val attributes for those.
attributesDictionary.add("data-val-" + rule.ValidationType, ruleErrorMessage);
//similarly for anything in the rule.ValidationParameters
attributesDictionary.Add("data-val-" + rule.ValidationType + "-" + parameterName, parameterValue);
}
Finally, in my editor template, call the html helper (which has a model type of `FooPart1) for each complex type property, e.g.
#Html.TextBoxFor(m => m.Foo, Html.MergeHtmlAttributes(new { #class="Bar"}))
#Html.TextBoxFor(m => m.Eey, Html.MergeHtmlAttributes())
I actually ended up creating a second interface (with the same signature as IClientValidatable) that allowed me to customize rules (primarily for error messages) for the individual fields of a complex type. I also extended the helper to take a string argument that could be formatted with my custom rules.
jQuery.validator.setDefaults({
success: "valid"
});
$( "#foo" ).validate({
rules:
{
rule1: {required: true, min: 3},
parent:
{
required: function(element) {return $("#age").val() < 13;}
}
}
});
Complex types seem to hassle me for no good reason so try the Jquery validator. Depending on what you're trying to validate it might get the job done.

Asp.net MVP - Creating dynamic controls, where?

So, I'm attempting to rewrite an old asp.net app and thought I'd do it in in MVP this time.
The app displays a dynamically generated form by placing labels and input fields in a table.
What I get from my Model is a list of entities that describe what control's should be rendered.
Now this list needs to be converted into a table with a lable and control on each row, but I can't decide where and how to do this.
These are the scenario's I could think of, but I have no idea which one is right according to MVP:
For each item create a table row in the presenter and call View.AddRow(row)
Create a list of table rows in the presenter and call View.AddRows(list)
For each item call View.CreateRow(info)
Any ideas?
Thanks!
The key of the MVP pattern is to separate the concerns between the view and the presenter. The presenter only has to set the list of entities and whether the data is presented as a table using a GridView, Repeater etc is the concern of the view.
If I were doing what you would describe I would use a 'view model' class to act as a wrapper for creating the controls:
public class DynamicControlViewModel
{
public enum ControlTypes
{
TextBox,
DropDown,
CheckBox
}
public string LabelValue { get; set; }
public ContrlTypes ControlType { get; set; }
}
My View would look as follows:
interface IDynamicControlsView
{
IEnumerable<DynamicControlViewModel> DynamicControls { set; }
}
And the presenter:
// This method would be called within your Presenter
public override void Initialize()
{
_view.DynamicControls = ConvertDataToViewModel(data); // your method to fetch the data would replace 'data'
}
The property setter on the view implementation (the .ASPX code behind) would look as:
public IEnumerable<DynamicControlViewModel> DynamicControls
{
set
{
foreach (DynamicControlViewModel model in value)
{
// build up user controls here....
switch (model.ControlType)
{
case DynamicControlViewModel.ContrlTypes.TextBox:
// add text box
break;
case DynamicControlViewModel.ContrlTypes.DropDown:
// add drop down
break;
case DynamicControlViewModel.ContrlTypes.CheckBox:
// add checkbox
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
Using the view model would make unit testing easier and keep a cleaner separation of concerns.

MVC2 and two different models using same controller method? Possible?

I don't know if this is the right way of doing this or not, but I am using Jquery and MVC2. I am using a the $.ajax method to make a call back to a controller to do some business logic on a .blur of a textbox.
I have two views that basically do the same thing with the common data, but are using different models. They both use the same controller. It might be easier to explain with code:
So here are the two models:
public class RecordModel {
public string RecordID { get; set; }
public string OtherProperties { get; set; }
}
public class SecondaryModel {
public string RecordID { get; set; }
public string OtherPropertiesDifferentThanOtherModel { get; set; }
}
There are two views that are strongly typed to these models. One is RecordModel, the other SecondaryModel.
Now on these views is a input="text" that is created via:
<%= Html.TextBoxFor(model => model.RecordID) %>
There is jQuery javascript that binds the .blur method to a call:
<script>
$('#RecordID').blur(function() {
var data = new Object();
data.RecordID = $('#RecordID').val();
// Any other stuff needed
$.ajax({
url: '/Controller/ValidateRecordID',
type: 'post',
dataType: 'json',
data: data,
success: function(result) {
alert('success: ' + result);
},
error: function(result) {
alert('failed');
}
});
}
</script>
The controller looks like:
[HttpPost]
public ActionResult ValidateRecordID(RecordModel model) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
Now this works fine if I explicitly name the RecordModel in the controller for the View that uses the RecordModel. However, the SecondaryModel view also tries to call this function, and it fails because it's expecting the RecordModel and not the SecondaryModel.
So my question is this. How can two different strongly typed views use the same Action in a controller and still adhering to the modeling pattern? I've tried abstract classes and interfaces (and changing the view pages to use the Interface/abstract class) and it still fails.
Any help? And sorry for the robustness of the post...
Thanks.
You could define an interface for those classes.
interface IRecord
{
string RecordID { get; set; }
string OtherProperties { get; set; }
}
and make the method receive the model by using that:
[HttpPost]
public ActionResult ValidateRecordID(IRecord model)
{
// TODO: Do some verification code here
return this.Json("Validated.");
}
If you only need the RecordID, you can just have the controller method take int RecordID and it will pull that out of the form post data instead of building the view model back up and providing that to your action method.
[HttpPost]
public ActionResult ValidateRecordID(int RecordID) {
// TODO: Do some verification code here
return this.Json("Validated.");
}
There is no direct way of binding data to a interface/abstract class. The DefaultModelBinder will try to instantiate that type, which is (by definition) impossible.
So, IMHO, you should not use that option. And if you still want to share the same controller action between the two views, the usual way of doing that would be using a ViewModel.
Make your strongly-typed views reference that viewmodel. Make the single shared action receive an instance of it. Inside the action, you will decide which "real" model should be used...
If you need some parameter in order to distinguish where the post came from (view 1 or 2), just add that parameter to the ajax call URL.
Of course, another way is keeping what you have already tried (interface/abstract class), but you'll need a custom Model Binder in that case... Sounds like overcoding to me, but it's your choice.
Edit After my dear SO fellow #Charles Boyung made a gracious (and wrong) comment below, I've come to the conclusion that my answer was not exactly accurate. So I have fixed some of the terminology that I've used here - hope it is clearer now.
In the case above your action could accept two strings instead of a concrete type.
Another possibility is having two actions. Each action taking one of your types. I'm assuming that functionality each type is basically the same. Once the values have been extracted hand them off to a method. In your case method will probably be the same for each action.
public ActionResult Method1(Record record)
{
ProcessAction(record.id, record.Property);
}
public ActionResult Action2(OtherRecord record)
{
ProcessAction(record.id, record.OtherProperty);
}
private void ProcessAction(string id, string otherproperity)
{
//make happen
}

Resources