I created a simple ASP.NET MVC version 1.0 application. I have a ProductController which has one action Index. In the view, I created a corresponding Index.aspx under Product subfolder.
Then I referenced the Spark dll and created Index.spark under the same Product view folder. The Application_Start looks like
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new Spark.Web.Mvc.SparkViewFactory());
ViewEngines.Engines.Add(new WebFormViewEngine());
}
My expectation is that since the Spark engine registers before default WebFormViewEngine, when browse the Index action in Product controller, the Spark engine should be used, and WebFormViewEngine should be used for all other urls.
However, the test shows that the Index action for Product controller also uses the WebFormViewEngine.
If I comment out the registration of WebFormViewEnginer (the last line in the code), I can see that the Index action is rendered by Spark engine and the rest urls generates an error (since the defualt engine is gone), it proves that all my Spark code is correct.
Now my question is how the view engine is resolved? Why the registration sequence does not take effect?
The order in which you register the view engines doesn't matter (much). Rather, view engines take a set of ViewLocationFormats, and if a particular view path fits the formatted name, that engine will be used. Only if you have conflicting formats does the registration order matter.
In the case of spark, views should have the .spark extension. WebFormViewEngine will respond to any with .aspx or .ascx extensions. And of course, as mentioned above, you can override any of this by changing the ViewLocationFormats supplied to the individual view engines.
Updated:
I took a look through the source of both SparkViewFactory and WebFormViewEngine (or more specifically, VirtualPathProviderViewEngine, which the latter derives from), and I can tell you why you're seeing this strange behaviour.
First of all, the Find method in the ViewEngineCollection class works like this (simplified):
foreach (IViewEngine engine in Items) {
// Query engine for cached view
}
foreach (IViewEngine engine in Items) {
// Query engine for uncached view
}
In other words, it will always try to find a cached view, in any engine, before resorting to uncached mode.
The way in which individual view engines implement this is the second overload of the FindView method, which takes a bool argument named useCache.
However, and here's where it all gets weird - the VirtualPathProviderViewEngine and SparkViewEngine have very different ideas of what the useCache argument means. There's too much code to repost here but the basic idea is:
The SparkViewFactory will look only in the cache if useCache is true. If it doesn't find anything, it automatically returns a "cache miss result" - i.e. nothing. On the other hand, if useCache is false, it will not look in the cache at all, it will skip the cache-checking step and go through the normal motions to resolve and create an actual view.
The VirtualPathProviderViewEngine, on the other hand, looks in the cache if useCache is true, and if it doesn't find the view in the cache, it goes off and creates a new one and adds that to the cache.
Both of these approaches work with respect to the way ViewEngineCollection performs its search.
In the case of spark, it "misses" on the first iteration of view engines, but "hits" on the second, and after that the view is added to the cache. No problem.
In the case of VirtualPathProviderViewEngine, it "misses" internally but returns a "hit" anyway on the first iteration, at which point the view is now cached.
So you should be able to see where the problem is here. The VirtualPathProviderViewEngine only appears to be taking precedence over the SparkViewEngine because the former always succeeds on the first (cached) iteration, but Spark only succeeds on the second (uncached) iteration.
In plain English, Spark really does get asked first, but replies: "No, I don't have that view yet. Try it without the cache instead." WebForms gets asked second, but automatically says "I didn't have that view, but I went and made one for you anyway, here it is.". And from that point on, the WebFormViewEngine always gets priority because it has the view cached and Spark doesn't.
Summary: Spark is getting priority, but due to a quirk in the way Spark treats the useCache argument, it's getting left in the dust when the Web Form engine is active at the same time. Either WebForm is over-eager or Spark is lazy, depending on your perspective.
Simply put, the solution is not to have conflicting views! If you've registered multiple view engines, then you should be treating any view name which can be handled by either/both of them as undefined behaviour.
Hmmm... Nope - all due respect webforms doesn't do anything beyond a cache-check when useCache is true. Same as Spark.
Actually - I think someone might have moved my cheese... Spark might have had a quirk added causing a false cache-miss during the useCache==true pass. If that's true it's more of a bug than different rules applied to that parameter.
Updated:
I was looking at MVC 2 originally - which is why I implied #Aaronaught's conclusions were incorrect. MVC 2 does not return a view on the first pass where useCache==true, which is different in MVC 1.0 which will resolve and populate.
So difference is between the way ASP.NET MVC 1.0 and ASP.NET MVC 2 are implemented. Spark and MVC 2 treat the useCache flag the same, and the order they are registered will give them priority.
Related
I am using the MvcSiteMapProvider Html Helper to create a navbar. The content of the navbar depends on the visitor rights therefore I am using security trimming to only display content which the person is authorized for. For performance improvement I am trying to cache this navbar.
The navbar is created in a partial view with the following content:
#Html.MvcSiteMap().Menu("MenuHelper", new { name = "MainMenu" })
Inside the layout file it is called by an action method which returns the partial view:
[System.Web.Mvc.OutputCache(Duration = 10, VaryByCustom = "User")]
[ChildActionOnly]
public ActionResult MainMenu()
{
return PartialView("MainMenu");
}
The caching of works fine at the root page of the sitemap. However when the cache duration runs out and the action method is called from deeper levels, no sitemap is returned.
When I disable security trimming or the output caching it works perfectly fine at all levels.
Is it possible that action method which returns the navbar, is called when the authorization data is unavailable and therefore returns a corrupt sitemap?
If you analyze the source for AuthorizeAttribute, you will note that it is not designed to work with child actions that are output cached (they go to some great lengths to ensure that child actions that are output cached will throw an exception).
Of course, it also won't work right if you have a custom AuthorizeAttribute that overrides OnAuthorization that does not duplicate this important logic.
However, there are a couple of things you can do to improve performance when using Security Trimming:
Make sure that your injection constructors are simple, especially on your controllers. If you have heavy processing in your constructors, it can really slow things down (with or without MvcSiteMapProvider, but Security Trimming makes this much more apparent).
If that doesn't improve things enough and you are not using a custom AuthorizeAttribute, you could use the roles attribute/property to duplicate your role logic into the SiteMap and remove the AuthorizeAttributeAclModule from your configuration (external DI only).
See this discussion for more details.
I'm looking at developing an application that will include a CMS. I'm a seasoned web forms developer but only really just moving into MVC.
I have a couple of questions that I hope some of you guys can answer:
First, my current web forms CMS allows users to create a page, and then "drop" any number of user controls onto that page they have created. The way I do this is to create an entry in the DB together with the path and then use the LoadControl method.
I can see I can do this with partial views, but partial views have no code behind. If I've potentially got 100 controls that people can drop onto a page, does this mean that the ViewBag in the controller needs to cater for all 100 controls just in case they are used on the view? For example, a web forms user control will contain logic: rptItems.DataSource = blah; rptItems.DataBind()
With MVC, I'm assuming that logic will be in the view controller and the view would access it by the ViewBag? I'm a little confused at how to do this.
Secondly, how would you handle deep routing?
EG:
Store/Products/Category is fine, but what about Store/Products/Category/Delivery/UK ? Would I need to set up a route in global.asax for each route I need? In web forms, I just called the ReWritePath method and handled the routing myself using regular expressions.
Thanks for the time to read this, and hopefully answer some of my queries
For your second question, (ie, "deep routing"), you can handle this within your controller instead of adding real routes. Each part of the url is available via the RouteData.Values collection inside of your controller action. So, your route may look like
~/Store/Products/Category/{*params}
Assuming typical route configuration, this would call the Category(...) action method on ~/areas/store/controllers/storeController, which could then grap delivery and uk from the RouteData.Values collection.
There are a lot of other approaches to this - storing routes in a database and using associated metadata to find the correct controller and method - but I think this is the simplest. Also, it may be obvious, but if you really only need two parameters beyond 'Category' in your example, you could just use
public ActionResult Category(string category, string region)
{
...
}
and a route:
~/store/{controller}/{action}/{category}/{region}/{*params}
Delivery and UK would be mapped to the the category and region parameters, respectively. Anything beyond uk would still be available via the RouteData.Values collection. This assumes that you don't have more specific routes, like
~/store/{controller}/{action}/{category}/{region}/{foo}/{bar}/{long_url}/{etc}
that would be a better match. ({*params} might conflict with the second route; you'll have to investigate to see if it's a problem.)
For your first question:
You can dynamically generate the view source and return it as a string from the controller, eliminating the need to pass a lot of stuff via ViewBag. If a virtual page from your CMS database requires inclusion of partial views, you would add the references to those components when generating the page. (This may or may not address your problem - if not, please provide more information.)
Should we move logic that supposes to be in Controller (like the data to render the partial view) to ActionFilter?
For example, I'm making a CMS web site. There should be a advertisement block to be rendered on several pages but not all the pages. Should I make an ActionFilter attribute like [ShowAd(categoryId)] and decorate the action methods with this attribute?
The implementation of this controller would include service calls to retrieve information from database, buildup view models and put in the ViewData. There would be a HtmlHelper to render the partial view using the data in ViewData if it exists.
That just seems yucky to me.
When I'm trying to figure out whether I need an ActionFilter, the first question I have is, Is this a cross-cutting concern?. Your particular use-case doesn't fit this, at first blush. The reason is, is that an ad is just another thing to render on a page. There's nothing special about it that makes it cross-cutting. If you replaced the word 'Ad' with 'Product' in your question, all the same facts would be true.
So there's that, and then there's the separation of concerns and testability. How testable are your controllers once you have this ActionFilter in place? It's something else you've got to mock out when testing, and what's worse is that you have to mock out those dependencies with every controller you add the ActionFilter to.
The second question I ask is, "How can I do this in a way that seems most idiomatic in the platform I'm using?"
For this particular problem, it sounds like a RenderAction and an AdController is the way to go.
Here's why:
An Ad is its own resource; it normally isn't closely tied to anything else on the page; it exists in its own little world, as it were.
It has its own data-access strategy
You don't really want to repeat the code to generate an Ad in every place you could use it (which is where a RenderPartial approach would take you)
So here's what such a beast would look like:
public AdController : Controller
{
//DI'd in
private AdRepository AdRepository;
[ChildActionOnly]
public ActionResult ShowAd(int categoryId)
{
Ad ad = Adrepository.GetAdByCategory(categoryId);
AdViewModel avm = new AdViewModel(ad);
return View(avm);
}
}
Then you could have a custom partial view that is set up around this, and there's no need to put a filter on every action (or every controller), and you don't have try to fit a square peg (an action filter) in a round hole (a dynamic view).
Adding an Ad to an existing page then becomes really easy:
<% Html.RenderAction("ShowAd", "Ad" new { categoryId = Model.CategoryId }); %>
If your ad system is simple enough, there is no reason you could/should not use an action filter to insert enough info into the view data to generate the ad in your view code.
For a simple ad system, say.. a single ad of a specific category shows up in the same place in the layout on every page and that's it, then there is no real argument of a better way except to prepare for future changes to the system. While those concerns may be legitimate, you may also have it on good authority that requirement will never change. But, even if requirements do change, having wrapped all the code that generates ads in one place is the most important aspect and will save you much more time up front than a more robust solution might. Obviously there are more than a few ways to wrap this code in a single place.
As for the way you are choosing to do it, I would keep your action filter cleaner to only have it insert the category into the view data and have all the magic happen inside your html helper which would take the category in as a parameter. Building up view models to shove into the view data is going to require a bit of extra work, and put code all over the place when it doesn't need to be there. Keep it simple and do all of the html generation inside of the html helper which is responsible for...building html.
I've created a fairly generic view engine that I created initially without aiming toward ASP.Net MVC. Now though, I think it would be a good idea to have it to where it can at least be easily used by MVC projects. I'm wondering if my project would map well to the ASP.Net MVC style though.
The problem I'm having is that my view engine generates everything at compile-time via T4 templates. This means that everything is statically typed for the most part. Most of MVC seems to be a bit loosely typed however.
So for some view you might have this code generated:
class MyView{
public string Foo{get;set;}
public int Bar{get;set;}
public string Render(){
return "This is my view: "+Foo+(string)Bar;
}
}
And because of how it works, even if there is a views/FooView.html file, it may get processed into a class named MyView.
So how exactly is the best way to assign ViewData to say Foo and Bar of MyView? Should I just impose the limitation that you can only use a single field in the views(basically being a ViewData) or?
The other major problem I see is that MVC is almost completely file based. When you say RenderView("MyView",data); it will look in /views/ for a file named MyView.aspx or whatever(you can change where it looks and the file extension of course). The problem is that MyView could have been compiled from a file named FooView.html. Should I basically just generate a huge list for every view available with their mappings from class name to filesystem name? Or is there a better way?
Note: Because I generate all the views(and possibly could generate the MVC view engine) from a T4 template, this means I could write huge lists and other extremely tedious or bad code. But I feel like there is a better way than a huge list in this case, and that there will be underlying problems with only keeping a list.
You may extend my T4 based view engine to create your class file from your model, compile it in memory and cache it, and pass your view model to the compiled assembly via reflection.
http://mvct4viewengine.codeplex.com/
I've been playing around with ASP.NET MVC and had a question. Or maybe its a concern that I am doing this wrong. Just working on a lame site to stretch my wings a bit. I am sorry this question is not at all concise.
Ok, here's the scenario. When the user visits home/index, the page should show a list of products and a list of articles. The file layout is such (DAL is my data access layer):
Controllers
Home
Index
Views
Home
Index inherits from ViewPage
Product
List inherits from ViewUserControl<IEnumerable<DAL.Product>>
Single inherits from ViewUserControl<DAL.Product>
Article
List inherits from ViewUserControl<IEnumerable<DAL.Article>>
Single inherits from ViewUserControl<DAL.Article>
Controllers.HomeController.Index produces a View whose ViewData contains two entries, a IEnumerable<DAL.Product> and a IEnumerable<DAL.Article>.
View.Home.Index will use those view entries to call:
Html.RenderPartial("~/Views/Product/List.ascx", ViewData["ProductList"])
and Html.RenderPartial("~/Views/Article/List.ascx", ViewData["ArticleList"])
View.Product.List will call
foreach(Product product in View.Model)
Html.RenderPartial("Single", product);
View.Article.List does something similar to View.Product.List
This approach fails however. The approach makes sense to me, but maybe someone with more experience with these MVC platforms will recognize a better way.
The above produces an error inside View.Product.List. The call to Html.RenderPartial("Single",...) complains that "Single" view was not found. The error indicates:
The partial view 'Single' could not be found. The following locations were searched:
~/Views/Home/Single.aspx
~/Views/Home/Single.ascx
~/Views/Shared/Single.aspx
~/Views/Shared/Single.ascx
Because I was calling RenderAction() from a view in Product, I expected the runtime to look for the "Single" view within Views\Product. It seems however the lookup is relative the controller which invoked the original view (/Controller/Home invoked /Views/Product) rather than the current view.
So I am able to fix this by changing Views\Product, such that:
View.Product.List will call
foreach(Product product in View.Model)
Html.RenderPartial("~/Views/Product/Single.ascx", product);
instead of
View.Product.List will call
foreach(Product product in View.Model)
Html.RenderPartial("Single", product);
This fix works but.. I do not understand why I needed to specify the full path of the view. It would make sense to me for the relative name to be interpreted relative to the current view's path rather than the original controller's view path. I cannot think of any useful case where interpreting the name relative to the controller's view instead of the current view is useful (except in the typical case where they are the same).
Around this time I should have a question mark? To emphasis this actually is a question.
Because I was calling RenderAction()
from a view in Product
...
I do not understand why I needed
to specify the full path of the view.
It would make sense to me for the
relative name to be interpreted
relative to the current view's path
rather than the original controller's
view path
The part I think you're misunderstanding is the "execution location" for lack of a better or official term. Paths are not relative to your view, not even your "controller's view" as you put it. They are relative to your request URL, which defines a controller context. I may not be saying it very well, but if you spent a little time in Reflector looking at how URLs and routes are resolved, I think this would all fall into place in your head.
[edit:
I was thinking, you have 2 cases:
the Home controller is the only one that ever references Product / Articles List user control
the user controls are shared by several controllers
In the first case, the view user controls really belong to the home controller and it makes sense to put them in the home controller folder. In the second case, it makes sense to place them in the shared folder since they will be shared by controllers.
In either case, maybe you can place them in a sub folder. Like Views/Home/Products and then try RendarPartial("Product/Single") and see what happens? I don't know if it would try to resolve it to: Home/Product/Single and then Shared/Product/Single or not. If sub folders work, it seems to allow the logical seperation of Product and Article, while showing that they are still members of either the Home controller or Shared by all controllers.
]
Check out this blog entry by Steve Sanderson:
http://blog.codeville.net/2008/10/14/partial-requests-in-aspnet-mvc/
What you are doing isn't wrong, but it does seem to sort of go against the convention of View/Controller folder names. That said, it makes sense to want to define controller-agnostic view user controls and nesting them seems valid. So I dunno!
Anyways, the link just describes a method of instead of using RenderPartial to render a use control, it defines a method of RenderPartialRequest that renders the return value (in your case a user control) of a controller action. So you could add a Product and Articles controller with an Action List that returns your user control, and then call those two actions from the Home/Index view. This seems more intuitive to me, but just an opinion.
He also mentions subcontrollers from MVC Contrib, and I'm pretty sure there is desire for something like this to be a part of ASP.NET MVC release.
From looking at the MVCStoreFront sample this is how they have everything structured for calling RenderPartial
Views
Shared
ProductSingle
ProductList
ArticleSingle
ArticleList
Then render them via:
<% Html.RenderPartial("ProductSingle", ViewData["ProductList"]); %>
<% Html.RenderPartial("ProductList", product); %>
<% Html.RenderPartial("ArticleSingle", article); %>
<% Html.RenderPartial("ArticleList", ViewData["ArticleList"]); %>