Fill ViewData on Every Action but not on renderaction - asp.net

I have a viewData that is required to be available on each page. If I fill this ViewData using onActionExecuting. It gets filled for every action even for actionresult of partial pages. I want ViewData to be get filled only once for each Page Load.
Any suggestion

You could use the OnActionExecuting event and test if the result returned b the view is a normal view or a partial view and based on that decide whether or not to add the information to ViewData:
public class MyActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData["foo"] = "bar";
}
}
}
Another possibility to have some common data available to all views is to externalize it in a separate child action and use the Html.Action helper to include it in your layout for example.

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.

About ASP.NET Web Pages Global

I am new learner to asp.net. I saw “_appstart.cshtml”, “_pagestart.cshtml” and “_viewstart.cshtml” which act like global headers or footer.
(1)If I want to trigger something right before the page is output, should I put the code in _viewstart.cshtml of others?
(2)Let C be the html code just before output, beside appending code to C can I replace code from C? Such as making all text uppercase or replace some text?
(3)Will asp.net cache this process so that I won't run each time?
benone
Answer to Point 1
The _ViewStart file can be used to define common view code that you want to execute at the start of each View’s rendering. For example, we could write code within our _ViewStart.cshtml file to programmatically set the Layout property for each View to be the SiteLayout.cshtml file by default
Actually it's like a BasePage in ASP.Net where we can keep the common code.
Or you can write the logic directly in the View like below.
#{
Layout = "~/Views/Shared/_Layout.cshtml";
if (Some Consition) {
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
}
Alternatively
You can override the Action Executing method, which executes before executing the Action Method. You can set it for a particular Action method or for the Complete Controller
Below is the code for setting it for Complete Controller.
protected override void OnActionExecuting(ActionExecutingContext ctx) {
base.OnActionExecuting(ctx);
}
Below is the Code for Setting it for Particular Action method
[MyAttribute(SomeProperty = "")]
public ActionResult Index()
{
return View("Index");
}
public class MyAttribute : ActionFilterAttribute
{
public string SomeProperty { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}
Answer to Point 2
You can use
var str = Html.Partial("_Partial_View_Name");
Partial returns an MvcHtmlString. You can intercept the output by setting it to a variable and make the necessary change.
Answer to Point 3
Yes. Below is the sample code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
[OutputCache(Duration=10, VaryByParam="none")]
public ActionResult Index()
{
return View();
}
}
}
The output of the Index() action is cached for 10 seconds

How to share a common object in each page request of a ASP.net MVC 4 webapp?

I come from "regular" asp.net so i'm a bit (totally) lost with MVC.
What I was doing with my own asp.net programmation pattern :
I have one custom class objet which represent the "page" and its properties (like mypage.loadJquery, mypage.isLogged, mypage.Title, custom cache logic, etc.)
This class is instanciate once on top of each ASHX page, I then manipulate a stringbuilder to produce HTML and spit it right at the browser at the end.
Having only one request on the ASHX page, i can use my page object instanciated at the top till the end when calling final response.write
Now i'm trying to go for MVC. I "kind of" understood the M/V/C model and the routing concept. I would like to keep my custom "page" object but I lost my page life cycle and I definitely don't know how to instanciate my page object ONCE in at the top of every call.
I need this instanciated ONCE shared object across every models, controllers, views, partial views, htmlhelper...
I realize MVC pattern might be confusing for me at this moment, bu how could I try to reproduce my need ?
(Very concrete exemple : On every request i need to check if the user is logged via his cookies. If it is I round trip the database to get user infos. Then I DO NEED THESE INFOS ON PRATICALLY EVERY model / controller / view of the app, but of course don't want to round back each time to security check and database querying, how can i have these info on the whole mvc cyle ?)
In my project I create interface IViewModel that contains all fields that I need in my layout/masterpage and set is as model of it so I can easily use them:
IViewModel.cs
public interface IViewModel
{
string Title { get; set; }
User User { get; set; }
}
Layout.cshtml
#model IViewModel
<html>
<head>
<title>#Model.Title</title>
</head>
<body>
#if (Model.User.IsAuthenticated) {
You are logged as #Model.User.Name
}
</body>
</html>
All my models implement that interface (or inherit from ViewModelBase that is default implementation of that class). Additionally I have custom action filter that check if returned ActionResult is (Partial)ViewResult and if Model of it implement my IViewModel interface and fill data in that interface.
public FillViewModelAttribute : ActionFilterAttribute
{
public override OnActionExecuted(ActionExecutedContext context)
{
var viewResult = context.Result as ViewResult;
if (viewResult != null && viewResult.Model is IViewModel)
{
var viewModel = (IViewModel)viewResult.Model;
// fill data
}
}
}
I created many projects like this. Basically, you can create a base controller class where all the other controllers inherit from it.
[Authorize]
public class BaseController : Controller
{
private Instructor _G_USER = null;
protected Instructor G_USER
{
get
{
if (_G_USER == null)
{
_G_USER = Your DB query here
ViewData["G_USER"] = _G_USER;
}
return _G_USER;
}
}
}
Then in your every child class, you can do
[Authorize]
public class YourController : BaseController
{
public ActionResult Index()
{
if(!G_USER.CAN_DO_THIS) throw new NoPermissionException();
return View();
}
}
To use the User in the view, create an extension method.
public static class ExtentionMethods
{
public static USER G_USER(this ViewPage page)
{
return (USER)page.ViewData["G_USER"];
}
}
Then use in the page like this
<%=this.G_USER().....%>

Access model from shared partial view independent of controller

I'm creating a simple webpage in asp.net mvc 3.
I have a sidebar that loads random citations, and the sidebar is on every page so it's a part of the layout, independent of the controller.
What is the correct way to access a datamodel from that view? Do I have to pass data from each controller?
My partial view file looks something like:
#model MvcApplication1.Models.CitationModel
#Model.Citation
But this results in a null reference.
The model is something like
public class CitationModel
{
public string Citation{ get { return "Test"; } }
}
I would do this with a child action. This way you can keep the view strongly typed (no viewbag or viewdata), without having to put it in a "master" viewmodel that gets sent to your layout:
<div id="sidebar">
#Html.Action("RandomCitations", "Citations")
</div>
In CitationsController:
[ChildActionOnly]
public PartialViewResult RandomCitations()
{
var model = new CitationModel();
// populate model
return PartialView(model);
}
Your view will stay the same, and will be injected into the sidebar div for every layout.
There are plenty of scenarios there.
For now for that cases I put model to view bag, and then getting it from viewbag on view.
I'd use a base controller class like this:
public class ApplicationController : Controller
{
public ApplicationController()
{
Citation c = getYourCitation();
ViewBag.Citation = c;
}
}
Get all your controllers to inherit from Application controller
public class HomeController : ApplicationController
{
//Controller code
}
Each view (including _Layout) will then be able to access the ViewBag
in _Layout.cshtml do this:
#Html.Partial("_CitationPartial", (Your.MidTier.Models.Citation)ViewBag.Citation)

Executing code before any action

I have the following requirement:
On every request to my web page, regardless of which action the user is trying to invoke, I need to call some code that checks if a resource is in place. If it is, then everything is fine and the action method should be called as normal.
However, if this resource is not available, I want all requests to return a separate page asking the user to select another resource from a list of available ones.
So is it possible to have one method run before any action method that have the option of cancelling the call to the action method, and doing something else instead?
Look at global action filters (available since asp.net mvc 3): http://msdn.microsoft.com/en-us/library/gg416513%28v=vs.98%29.aspx
Basically, in your Global.asax, you can register the filter globally during your application startup (in Application_Start()) with:
GlobalFilters.Filters.Add(new MyActionFilterAttribute());
You can then override the OnActionExecuting method, and set the Result property with a RedirectToRouteResult.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (IsMyResourceAvailable())
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {
{ "Controller", "YourControllerName" },
{ "Action", "YourAction" }
});
}
base.OnActionExecuting(filterContext);
}
MVC provides several hooks to do this.
In a base controller, you can override Controller.OnActionExecuting(context) which fires right before the action executes. You can set context.Result to any ActionResult (such as RedirectToAction) to override the action.
Alternatively, you can create an ActionFilterAttribute, and exactly like above, you override the OnActionExecuting method. Then, you just apply the attribute to any controller that needs it.

Resources