Separating model concerns - c#

Any given model in my application might implement the IHasOpenGraphMetadata, if that's the case, a call to #Html.Partial("_OpenGraphMetadata") in my layout will render the related metadata, by accessing the model through that interface.
All's good and clean. That is until I wanted to do that from a partial action.
In /Home/Index I have the following call: #Html.Action("List", "Posts")
This returns:
public ActionResult List(long? timestamp = null, int count = 8)
{
IEnumerable<Post> posts = postService.GetLatest(timestamp, count);
PostListModel model = mapper.Map<IEnumerable<Post>, PostListModel>(posts);
return PartialView(model);
}
The issue becomes apparent here: the actual model for /Home/Index is null, while the actual metadata is in the /Posts/List partial view result
I went with this in my partial which renders the og:data ("_OpenGraphMetadata"):
#{
OpenGraphModel openGraph = GetOpenGraphMetadata();
if (openGraph != null)
{
#OpenGraphMetaProperty("title", openGraph.Title)
#OpenGraphMetaProperty("description", openGraph.Description)
#OpenGraphMetaProperty("url", openGraph.Url)
#OpenGraphMetaProperty("image", openGraph.Image)
}
}
#helper OpenGraphMetaProperty(string property, string value)
{
if (!value.NullOrBlank())
{
<meta property="og:#property" content="#value" />
}
}
#functions
{
private OpenGraphModel GetOpenGraphMetadata()
{
IHasOpenGraphMetadata model = Model as IHasOpenGraphMetadata;
if (model != null)
{
return model.OpenGraph;
}
return Context.Items[Constants.OpenGraphContextItem] as OpenGraphModel;
}
}
I figure this is pretty decent, try to extract the metadata from the actual model, and if the interface is not implemented, then access the HttpContext and look for the metadata there. The question now is, how should I be putting this metadata in the HttpContext?
I don't really want to pollute my action methods with calls like:
HttpContext.Items[Constants.OpenGraphContextItem] = model.OpenGraph;
But what other options do I have? Should I be doing this in a global action filter? a result filter? Is there an alternative to these options?
Update
It doesn't seem possible to do this in an action filter, the way I figured out I could do this was overriding the base controller's PartialView, and both View overloads, but this seems like a really unpolished approach, there must be a better way to accomplish this.

Related

Posting a polymorpic object to a controller action [duplicate]

This question has been asked before in earlier versions of MVC. There is also this blog entry about a way to work around the problem. I'm wondering if MVC3 has introduced anything that might help, or if there are any other options.
In a nutshell. Here's the situation. I have an abstract base model, and 2 concrete subclasses. I have a strongly typed view that renders the models with EditorForModel(). Then I have custom templates to render each concrete type.
The problem comes at post time. If I make the post action method take the base class as the parameter, then MVC can't create an abstract version of it (which i would not want anyways, i'd want it to create the actual concrete type). If I create multiple post action methods that vary only by parameter signature, then MVC complains that it's ambiguous.
So as far as I can tell, I have a few choices on how to solve this proble. I don't like any of them for various reasons, but i will list them here:
Create a custom model binder as Darin suggests in the first post I linked to.
Create a discriminator attribute as the second post I linked to suggests.
Post to different action methods based on type
???
I don't like 1, because it is basically configuration that is hidden. Some other developer working on the code may not know about it and waste a lot of time trying to figure out why things break when changes things.
I don't like 2, because it seems kind of hacky. But, i'm leaning towards this approach.
I don't like 3, because that means violating DRY.
Any other suggestions?
Edit:
I decided to go with Darin's method, but made a slight change. I added this to my abstract model:
[HiddenInput(DisplayValue = false)]
public string ConcreteModelType { get { return this.GetType().ToString(); }}
Then a hidden automatically gets generated in my DisplayForModel(). The only thing you have to remember is that if you're not using DisplayForModel(), you'll have to add it yourself.
Since I obviously opt for option 1 (:-)) let me try to elaborate it a little more so that it is less breakable and avoid hardcoding concrete instances into the model binder. The idea is to pass the concrete type into a hidden field and use reflection to instantiate the concrete type.
Suppose that you have the following view models:
public abstract class BaseViewModel
{
public int Id { get; set; }
}
public class FooViewModel : BaseViewModel
{
public string Foo { get; set; }
}
the following controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new FooViewModel { Id = 1, Foo = "foo" };
return View(model);
}
[HttpPost]
public ActionResult Index(BaseViewModel model)
{
return View(model);
}
}
the corresponding Index view:
#model BaseViewModel
#using (Html.BeginForm())
{
#Html.Hidden("ModelType", Model.GetType())
#Html.EditorForModel()
<input type="submit" value="OK" />
}
and the ~/Views/Home/EditorTemplates/FooViewModel.cshtml editor template:
#model FooViewModel
#Html.EditorFor(x => x.Id)
#Html.EditorFor(x => x.Foo)
Now we could have the following custom model binder:
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType(
(string)typeValue.ConvertTo(typeof(string)),
true
);
if (!typeof(BaseViewModel).IsAssignableFrom(type))
{
throw new InvalidOperationException("Bad Type");
}
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
The actual type is inferred from the value of the ModelType hidden field. It is not hardcoded, meaning that you could add other child types later without having to ever touch this model binder.
This same technique could be easily be applied to collections of base view models.
I have just thought of an intersting solution to this problem. Instead of using Parameter bsed model binding like this:
[HttpPost]
public ActionResult Index(MyModel model) {...}
I can instead use TryUpdateModel() to allow me to determine what kind of model to bind to in code. For example I do something like this:
[HttpPost]
public ActionResult Index() {...}
{
MyModel model;
if (ViewData.SomeData == Something) {
model = new MyDerivedModel();
} else {
model = new MyOtherDerivedModel();
}
TryUpdateModel(model);
if (Model.IsValid) {...}
return View(model);
}
This actually works a lot better anyways, because if i'm doing any processing, then I would have to cast the model to whatever it actually is anyways, or use is to to figure out the correct Map to call with AutoMapper.
I guess those of us who haven't been using MVC since day 1 forget about UpdateModel and TryUpdateModel, but it still has its uses.
It took me a good day to come up with an answer to a closely related problem - although I'm not sure it's precisely the same issue, I'm posting it here in case others are looking for a solution to the same exact problem.
In my case, I have an abstract base-type for a number of different view-model types. So in the main view-model, I have a property of an abstract base-type:
class View
{
public AbstractBaseItemView ItemView { get; set; }
}
I have a number of sub-types of AbstractBaseItemView, many of which define their own exclusive properties.
My problem is, the model-binder does not look at the type of object attached to View.ItemView, but instead looks only at the declared property-type, which is AbstractBaseItemView - and decides to bind only the properties defined in the abstract type, ignoring properties specific to the concrete type of AbstractBaseItemView that happens to be in use.
The work-around for this isn't pretty:
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
// ...
public class ModelBinder : DefaultModelBinder
{
// ...
override protected ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.IsAbstract && bindingContext.Model != null)
{
var concreteType = bindingContext.Model.GetType();
if (Nullable.GetUnderlyingType(concreteType) == null)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(concreteType).GetTypeDescriptor(concreteType);
}
}
return base.GetTypeDescriptor(controllerContext, bindingContext);
}
// ...
}
Although this change feels hacky and is very "systemic", it seems to work - and does not, as far as I can figure, pose a considerable security-risk, since it does not tie into CreateModel() and thus does not allow you to post whatever and trick the model-binder into creating just any object.
It also works only when the declared property-type is an abstract type, e.g. an abstract class or an interface.
On a related note, it occurs to me that other implementations I've seen here that override CreateModel() probably will only work when you're posting entirely new objects - and will suffer from the same problem I ran into, when the declared property-type is of an abstract type. So you most likely won't be able to edit specific properties of concrete types on existing model objects, but only create new ones.
So in other words, you will probably need to integrate this work-around into your binder to also be able to properly edit objects that were added to the view-model prior to binding... Personally, I feel that's a safer approach, since I control what concrete type gets added - so the controller/action can, indirectly, specify the concrete type that may be bound, by simply populating the property with an empty instance.
Using Darin's method to discriminate your model types via a hidden field in your view, I would recommend that you use a custom RouteHandler to distinguish your model types, and direct each one to a uniquely named action on your controller. For example, if you have two concrete models, Foo and Bar, for your Create action in your controller, make a CreateFoo(Foo model) action and a CreateBar(Bar model) action. Then, make a custom RouteHandler, as follows:
public class MyRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var httpContext = requestContext.HttpContext;
var modelType = httpContext.Request.Form["ModelType"];
var routeData = requestContext.RouteData;
if (!String.IsNullOrEmpty(modelType))
{
var action = routeData.Values["action"];
routeData.Values["action"] = action + modelType;
}
var handler = new MvcHandler(requestContext);
return handler;
}
}
Then, in Global.asax.cs, change RegisterRoutes() as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
AreaRegistration.RegisterAllAreas();
routes.Add("Default", new Route("{controller}/{action}/{id}",
new RouteValueDictionary(
new { controller = "Home",
action = "Index",
id = UrlParameter.Optional }),
new MyRouteHandler()));
}
Then, when a Create request comes in, if a ModelType is defined in the returned form, the RouteHandler will append the ModelType to the action name, allowing a unique action to be defined for each concrete model.

How to validate multiple ViewModels in single view by condition using ModelState.IsValid

I have a problem with validation of one view with multiple ViewModels. My situation is, that I have one Basic form, which is same for lot of pages. By ID parameter, I render new external fields to this Basic form. These external fields are type of ActionResult, using own ViewModel and own Controller. In Main controller on Post action I want to control if ModelState.IsValid, but I have problem - it validate all ViewModels of all external fields, but I want to validate only ViewModel of active external fields (and Basic form too).
It looks like this:
ViewModel of all view models
public class AllFieldsVm
{
public BasicFormVm BasicFormVm { get; set; }
public ExternalFieldXyVm ExternalFieldXyVm { get; set; }
public AnotherExternalFieldVm AnotherExternalFieldVm { get; set; }
}
In controller of external fields I create new instance of AllFieldsVm and in this create new instance of ExternalFieldXyVm (if I need, I prefill these fields). This I render whitout layout like partial view (using #{Html.RenderAction("Action", "Controller", new {#someOptionalData = value});} ), when some condition is true.
In controller of Basic form on Post action I have something like this and I want to use something like this code if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)):
[POST("someurl-id{someId}")]
public ActionResult SaveFormData(int someId, AllFieldsVm model)
{
//Here I want something like
//if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)) or something like that...
var se = new SomeEntity();
se.property1 = model.property1;
se.property2 = model.property2;
using (var dbc = _db.Database.BeginTransaction())
{
try
{
_db.Add(se);
_db.SaveChanges();
//My Condition - when save external data
if (someId == (int) MovementTypes.SomeEnumInt)
{
var rd = new ExternalFieldEntity
{
PropertyA = se.property0,
PropertyB = Convert.ToDateTime(model.ExternalFieldXyVm.SomeExternalFieldName)
};
_db.Add(rd);
_db.SaveChanges();
}
dbc.Commit();
}
catch (Exception)
{
dbc.Rollback();
}
}
return RedirectToAction("Action", "Controller");
}
So, my question is, how can I validate ExternalFieldXyVm separatly based on some conditions?
Is it possible, or I have to create all own validators, without using basic DataAnnotations or FluentValidation? I have no experience with these types of forms, so please be patient...
Thanks to all for help!!
Great, I got it. I play with this for two days, don't know how it is possible that I didn't see that.
Result is: When view with own view model which is included in main viewmodel, isn't rendered into view, this viewmodel is not validate on post action. So my Basic form is validate everytime, and ExternalFields are validate only when are rendered. So sorry, for so stupid question....

Posting with multiple models in a view

So I have a View that contains 2 models. Each model has its own form and submit button.
Currently, I have both submit buttons are processed by the same controller method and use reflection to figure out which model type was passed. But it seems like there would be a better way... any ideas?
I have something like this:
Models:
public class Model1
{
// Elements
}
public class Model2
{
// Elements
}
Controller:
public ViewResult ConMeth(Object model)
{
Type t = model.GetType();
if(t == typeof(Model1)
{
// Do work for Model1
}
else if(t == typeof(Model2)
{
// Do work for Model2
}
else
{
// Do something else...
}
}
If you show your view info, I suspect you've got two seperate things happening in the view. Just put each thing in it's own form and use the
#using (Html.BeginForm(...)){}
and specify the actions by name and the controller (if necessary) in the BeginForm params... That should get rid of the ambiguous reference error
Here is an example w/ the older (not razor) tags
You can use a Tuple<> in your view to have two view models, and then in the #Html.BeginForm() helper method for each form, you can specify POSTs to two different controllers to process your form data.
#model Tuple<ProjectName.Models.Model1, ProjectName.Models.Model2>

How to show Alert Message like "successfully Inserted" after inserting to DB using ASp.net MVC3

How to write a code for displaying the alert message: "Successfully registered", after user data is stored in database, using MVC
I am using Asp.Net MVC3, C#, Entity Model.
Try using TempData:
public ActionResult Create(FormCollection collection) {
...
TempData["notice"] = "Successfully registered";
return RedirectToAction("Index");
...
}
Then, in your Index view, or master page, etc., you can do this:
<% if (TempData["notice"] != null) { %>
<p><%= Html.Encode(TempData["notice"]) %></p>
<% } %>
Or, in a Razor view:
#if (TempData["notice"] != null) {
<p>#TempData["notice"]</p>
}
Quote from MSDN (page no longer exists as of 2014, archived copy here):
An action method can store data in the controller's TempDataDictionary object before it calls the controller's RedirectToAction method to invoke the next action. The TempData property value is stored in session state. Any action method that is called after the TempDataDictionary value is set can get values from the object and then process or display them. The value of TempData persists until it is read or until the session times out. Persisting TempData in this way enables scenarios such as redirection, because the values in TempData are available beyond a single request.
The 'best' way to do this would be to set a property on a view object once the update is successful. You can then access this property in the view and inform the user accordingly.
Having said that it would be possible to trigger an alert from the controller code by doing something like this -
public ActionResult ActionName(PostBackData postbackdata)
{
//your DB code
return new JavascriptResult { Script = "alert('Successfully registered');" };
}
You can find further info in this question - How to display "Message box" using MVC3 controller
Personally I'd go with AJAX.
If you cannot switch to #Ajax... helpers, I suggest you to add a couple of properties in your model
public bool TriggerOnLoad { get; set; }
public string TriggerOnLoadMessage { get; set: }
Change your view to a strongly typed Model via
#using MyModel
Before returning the View, in case of successfull creation do something like
MyModel model = new MyModel();
model.TriggerOnLoad = true;
model.TriggerOnLoadMessage = "Object successfully created!";
return View ("Add", model);
then in your view, add this
#{
if (model.TriggerOnLoad) {
<text>
<script type="text/javascript">
alert('#Model.TriggerOnLoadMessage');
</script>
</text>
}
}
Of course inside the tag you can choose to do anything you want, event declare a jQuery ready function:
$(document).ready(function () {
alert('#Model.TriggerOnLoadMessage');
});
Please remember to reset the Model properties upon successfully alert emission.
Another nice thing about MVC is that you can actually define an EditorTemplate for all this, and then use it in your view via:
#Html.EditorFor (m => m.TriggerOnLoadMessage)
But in case you want to build up such a thing, maybe it's better to define your own C# class:
class ClientMessageNotification {
public bool TriggerOnLoad { get; set; }
public string TriggerOnLoadMessage { get; set: }
}
and add a ClientMessageNotification property in your model. Then write EditorTemplate / DisplayTemplate for the ClientMessageNotification class and you're done. Nice, clean, and reusable.
Little Edit
Try adding
return new JavascriptResult() { Script = "alert('Successfully registered');" };
in place of
return RedirectToAction("Index");

Security in MVC Views

In my MVC application I have a few different roles: Admin, General User, etc., etc.
I know that I can apply security to my Controllers via the Authorize attribute:
[Authorize(Roles="Admin")]
public ActionResult Create()
{
return View();
}
But I also need to apply some security to the Views to not display certain sections of the View to certain roles:
#if( User.IsInRole("Admin") )
{
#Html.ActionLink("Create", "Create")
}
Is it better to do it the above way, or handle this sort of security in a ViewModel:
public ActionResult Index()
{
var model = new IndexViewModel();
model.CanCreate = User.IsInRole("Admin");
return View(model);
}
View:
#( Model.CanCreate )
{
#Html.ActionLink("Create", "Create")
}
Does the second method have any benefits compared to the first or is it just a preference thing?
The second way is more preferred, as your business logic will stay at model level.
In your example, business logic is very simple. However, imagine that requirements have changed and now not only Admins can create content, but also General Users that signed up more than 1 month ago. With business logic in view you'd have to update all your views.
One way I have done this before is creating an action filter that inherits from the AuthorizeAttribute. The filter can be called something like DisplayIfAuthorizedAttribute, and in addition to the standard AuthorizeAttribute properties, has a property called ViewNameIfNotAuthorized.
The attribute calls the base method to do authorization, and if it fails, returns the ViewNameIfNotAuthorized view. Otherwise, it allows the action method to proceed normally.
You would then render these partial views via action methods, and call the action methods through Html.RenderAction or Html.Action in your parent view. Those action methods would be decorated with the attribute.
You now have a standardized way to do this and no authorization code polluting the internals of your action methods.
This is what the filter would look like:
public class DisplayIfAuthorizedAttribute : System.Web.Mvc.AuthorizeAttribute
{
private string _ViewNameIfNotAuthorized;
public DisplayIfAuthorizedAttribute(string viewNameIfNotAuthorized = null)
{
_ViewNameIfNotAuthorized = viewNameIfNotAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = base.AuthorizeCore(filterContext.HttpContext);
if (!isAuthorized)
{
filterContext.Result = GetFailedResult();
}
}
private ActionResult GetFailedResult()
{
if (!String.IsNullOrEmpty(_ViewNameIfNotAuthorized))
{
return new ViewResult { ViewName = _ViewNameIfNotAuthorized };
}
else
return new EmptyResult();
}
}
Your action method would be decorate as:
[DisplayIfAuthorized("EmptyView", Roles="Admin")]
public ViewResult CreateLink()
{
return View("CreateLink");
}
You may need both...
Note that the 2nd one alone would not be secure, a user might be able to construct the URL for the actionlink in the browsers addressbar. So you absolutely need the attribute for security.
The second one is more a matter of user-friendliness or UI design. Maybe you want the user to be able to click Create and then have a choice to login differently.
Check the authorization in your controller and prepare the Viewmodel for the view according to your role's rules.
The views are used to simply show data. So, imo, they don't have to do roles check etc..
So prepare the ViewModel with the data it should have and let the View only render it. (the boolean property you're using it's enough imo)

Categories