About ASP.NET Web Pages Global - asp.net

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

Related

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().....%>

Fill ViewData on Every Action but not on renderaction

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.

Best way to do global viewdata in an area of my ASP.NET MVC site?

I have an several controllers where I want every ActionResult to return the same viewdata. In this case, I know I will always need basic product and employee information.
Right now I've been doing something like this:
public ActionResult ProductBacklog(int id) {
PopulateGlobalData(id);
// do some other things
return View(StrongViewModel);
}
Where PopulateGlobalData() is defined as:
public void PopulateGlobalData(int id) {
ViewData["employeeName"] = employeeRepo.Find(Thread.CurrentPrincipal.Identity.Name).First().FullName;
ViewData["productName"] = productRepo.Find(id).First().Name;
}
This is just pseudo-code so forgive any obvious errors, is there a better way to be doing this? I thought of having my controller inherit a class that pretty much does the same thing you see here, but I didn't see any great advantages to that. It feels like what I'm doing is wrong and unmaintable, what's the best way to go about this?
You could write a custom action filter attribute which will fetch this data and store it in the view model on each action/controller decorated with this attribute.
public class GlobalDataInjectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string id = filterContext.HttpContext.Request["id"];
// TODO: use the id and fetch data
filterContext.Controller.ViewData["employeeName"] = employeeName;
filterContext.Controller.ViewData["productName"] = productName;
base.OnActionExecuted(filterContext);
}
}
Of course it would much cleaner to use a base view model and strongly typed views:
public class GlobalDataInjectorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string id = filterContext.HttpContext.Request["id"];
// TODO: use the id and fetch data
var model = filterContext.Controller.ViewData.Model as BaseViewModel;
if (model != null)
{
model.EmployeeName = employeeName;
model.ProductName = productName;
}
base.OnActionExecuted(filterContext);
}
}
Now all that's left is to is to decorate your base controller with this attribute:
[GlobalDataInjector]
public abstract class BaseController: Controller
{ }
There's another more interesting solution which I personally prefer and which involves child actions. Here you define a controller which handles the retrieval of this information:
public class GlobalDataController: Index
{
private readonly IEmployeesRepository _employeesRepository;
private readonly IProductsRepository _productsRepository;
public GlobalDataController(
IEmployeesRepository employeesRepository,
IProductsRepository productsRepository
)
{
// usual constructor DI stuff
_employeesRepository = employeesRepository;
_productsRepository = productsRepository;
}
[ChildActionOnly]
public ActionResult Index(int id)
{
var model = new MyViewModel
{
EmployeeName = _employeesRepository.Find(Thread.CurrentPrincipal.Identity.Name).First().FullName,
ProductName = _productsRepository.Find(id).First().Name;
};
return View(model);
}
}
And now all that's left is to include this wherever needed (probably the master page if global):
<%= Html.Action("Index", "GlobalData", new { id = Request["id"] }) %>
or if the id is part of the routes:
<%= Html.Action("Index", "GlobalData",
new { id = ViewContext.RouteData.GetRequiredString("id") }) %>
I thought of having my controller inherit a class that pretty much does the same thing you see here, but I didn't see any great advantages to that.
This is the way to go, in my opinion. You'd create a base Controller class that would provide this functionality. If you are familiar with the ASP.NET WebForms model then this is similar to creating a custom base Page class.
As to the advantages of putting it in a base class, the main advantages are readability, maintainability and reusability. If you copy and paste the above method into each controller that needs it, you are going to have a more difficult time if, down the road, you need to add new information to the ViewData collection.
In short, anytime you catch yourself copying and pasting code among classes or views in your application you should stop and think about how to put such logic in a single place. For more, read up on DRY - Don't Repeat Yourself.

System.Web.Routing with WebForms - picking up variables in the target page

I have the pattern User/{domain}/{username} set up via Routing. Everything works except for one thing. I can't figure out how to get the domain and username variables passed to my redirected page. Below is my GetHttpHandler method from my IRouteHandler implementation.
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string basePath;
basePath = "~/UserPage.aspx";
string domain = requestContext.RouteData.GetRequiredString("domain");
string username = requestContext.RouteData.GetRequiredString("username");
string virtualPath =
string.Format(basePath + "?domain={0}&username={1}", domain, username);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
I get the error from the last line of code:
UserPage.aspx?domain=SOMEDOMAIN&username=SOMEUSER is not a valid virtual path.
So how are you supposed to pass variables to the target page? what am I missing?
I think I solved this one myself.
Found this loop
foreach (KeyValuePair<string, object> token in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items.Add(token.Key, token.Value);
}
from http://www.codethinked.com/post/2008/08/20/Exploring-SystemWebRouting.aspx
Its like the 4th code sample down.
UPDATE:
Not sure if this will work... requestContext.HttpContext seems to be "readonly". Back to the drawing board.
UPDATE 2:
Looks like this will work if you add in a reference to System.Web.Abstractions
Started mucking around with things and saw the IHttpHandler interface provides the RequestContext to the GetHttpHandler method.
So, I modified my base page class (I always put a layer between System.Web.UI.Page and my own pages, calling it BasePage or similar just for the purpose). So I added a public property on PVBasePage to receive a RequestContext object.
public RequestContext RequestContext { get; set; }
Then, my Routing class code is as follows:
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
// create the page object as my own page...
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath
, typeof(PVBasePage)) as PVBasePage;
// pass in the request context
page.RequestContext = requestContext;
// return this page in the form of a IHttpHandler
return page as IHttpHandler;
}
So instead of, as in the sample code, creating the instance directly as the IHttpHandler, I create it as my own page. Set the request context property, and then return the page to the caller AS a IHttpHandler.
Tested and it works. WOO HOO!
Then in the instance page, you can hit the RequestContext.GetValues collection to read out your passed in parameters.
HTH
#B.Tyndall
I just got this working with a solution similar to yours.
found at: http://msmvps.com/blogs/luisabreu/archive/2008/03/12/using-the-routing-mvc-api-with-classic-asp-net.aspx
foreach (var aux in requestContext.RouteData.Values)
{
HttpContext.Current.Items[aux.Key] = aux.Value;
}
So in effect you're no longer using the Request.QueryString but instead Context.Items collection
HttpContext.Current.Items["RouteName"]
or
Context.Items["RouteName"]
It appears as though other are also taking the route (no pun intended) of putting the parameters in the context Items collection.
http://bbits.co.uk/blog/archive/2008/05/19/using-asp.net-routing-independent-of-mvc---passing-parameters-to.aspx
I combined a couple of these approaches for pages that have a specific parameter, I created a UserNameRouteHandler for pages that accept that type of parameter. In my PageBase class I checked the context items for that parameter and then set a property so that my pages that inherit from PageBase can use it.
public class UserNameRouteHandler : IRouteHandler
{
#region Implementation of IRouteHandler
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string pageName = requestContext.RouteData.GetRequiredString("PageName");
string employeeUserName = requestContext.RouteData.GetRequiredString("UserName");
if(!string.IsNullOrEmpty(employeeUserName))
{
requestContext.HttpContext.Items["UserName"] = employeeUserName;
}
pageName = pageName.ToLower() == "home" ? "default" : pageName;
string virtualPath = string.Format("~/{0}.aspx", pageName);
return (Page)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(Page));
}
#endregion
}
And in my OnLoad of PageBase I set the property to pages that need it can have it...definitely looking for a more elegant solution though.
protected override void OnLoad(EventArgs e)
{
if (!IsPostBack)
{
if (Context.Items["UserName"] != null)
{
EmployeeUserName = Context.Items["UserName"].ToString();
}
}
base.OnLoad(e);
}

ASP.NET - Avoid hardcoding paths

I'm looking for a best practice solution that aims to reduce the amount of URLs that are hard-coded in an ASP.NET application.
For example, when viewing a product details screen, performing an edit on these details, and then submitting the changes, the user is redirected back to the product listing screen. Instead of coding the following:
Response.Redirect("~/products/list.aspx?category=books");
I would like to have a solution in place that allows me to do something like this:
Pages.GotoProductList("books");
where Pages is a member of the common base class.
I'm just spit-balling here, and would love to hear any other way in which anyone has managed their application redirects.
EDIT
I ended up creating the following solution: I already had a common base class, to which I added a Pages enum (thanks Mark), with each item having a System.ComponentModel.DescriptionAttribute attribute containing the page's URL:
public enum Pages
{
[Description("~/secure/default.aspx")]
Landing,
[Description("~/secure/modelling/default.aspx")]
ModellingHome,
[Description("~/secure/reports/default.aspx")]
ReportsHome,
[Description("~/error.aspx")]
Error
}
Then I created a few overloaded methods to handle different scenarios. I used reflection to get the URL of the page through it's Description attribute, and I pass query-string parameters as an anonymous type (also using reflection to add each property as a query-string parameter):
private string GetEnumDescription(Enum value)
{
Type type = value.GetType();
string name = Enum.GetName(type, value);
if (name != null)
{
FieldInfo field = type.GetField(name);
if (field != null)
{
DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attr != null)
return attr.Description;
}
}
return null;
}
protected string GetPageUrl(Enums.Pages target, object variables)
{
var sb = new StringBuilder();
sb.Append(UrlHelper.ResolveUrl(Helper.GetEnumDescription(target)));
if (variables != null)
{
sb.Append("?");
var properties = (variables.GetType()).GetProperties();
foreach (var property in properties)
sb.Append(string.Format("{0}={1}&", property.Name, property.GetValue(variables, null)));
}
return sb.ToString();
}
protected void GotoPage(Enums.Pages target, object variables, bool useTransfer)
{
if(useTransfer)
HttpContext.Current.Server.Transfer(GetPageUrl(target, variables));
else
HttpContext.Current.Response.Redirect(GetPageUrl(target, variables));
}
A typical call would then look like so:
GotoPage(Enums.Pages.Landing, new {id = 12, category = "books"});
Comments?
I'd suggest that you derive your own class ("MyPageClass") from the Page class and include this method there:
public class MyPageClass : Page
{
private const string productListPagePath = "~/products/list.aspx?category=";
protected void GotoProductList(string category)
{
Response.Redirect(productListPagePath + category);
}
}
Then, in your codebehind, make sure that your page derives from this class:
public partial class Default : MyPageClass
{
...
}
within that, you can redirect just by using:
GotoProductList("Books");
Now, this is a bit limited as is since you'll undoubtedly have a variety of other pages like the ProductList page. You could give each one of them its own method in your page class but this is kind of grody and not smoothly extensible.
I solve a problem kind of like this by keeping a db table with a page name/file name mapping in it (I'm calling external, dynamically added HTML files, not ASPX files so my needs are a bit different but I think the principles apply). Your call would then use either a string or, better yet, an enum to redirect:
protected void GoToPage(PageTypeEnum pgType, string category)
{
//Get the enum-to-page mapping from a table or a dictionary object stored in the Application space on startup
Response.Redirect(GetPageString(pgType) + category); // *something* like this
}
From your page your call would be: GoToPage(enumProductList, "Books");
The nice thing is that the call is to a function defined in an ancestor class (no need to pass around or create manager objects) and the path is pretty obvious (intellisense will limit your ranges if you use an enum).
Good luck!
You have a wealth of options availible, and they all start with creating a mapping dictionary, whereas you can reference a keyword to a hard URL. Whether you chose to store it in a configuration file or database lookup table, your options are endless.
You have a huge number of options available here. Database table or XML file are probably the most commonly used examples.
// Please note i have not included any error handling code.
public class RoutingHelper
{
private NameValueCollecton routes;
private void LoadRoutes()
{
//Get your routes from db or config file
routes = /* what ever your source is*/
}
public void RedirectToSection(string section)
{
if(routes == null) LoadRoutes();
Response.Redirect(routes[section]);
}
}
This is just sample code, and it can be implemented any way you wish. The main question you need to think about is where you want to store the mappings. A simple xml file could do it:
`<mappings>
<map name="Books" value="/products.aspx/section=books"/>
...
</mappings>`
and then just load that into your routes collection.
public class BasePage : Page
{
public virtual string GetVirtualUrl()
{
throw new NotImplementedException();
}
public void PageRedirect<T>() where T : BasePage, new()
{
T page = new T();
Response.Redirect(page.GetVirtualUrl());
}
}
public partial class SomePage1 : BasePage
{
protected void Page_Load()
{
// Redirect to SomePage2.aspx
PageRedirect<SomePage2>();
}
}
public partial class SomePage2 : BasePage
{
public override string GetVirtualUrl()
{
return "~/Folder/SomePage2.aspx";
}
}

Resources