I've read Spring 3 docs about view resolvers... But in my project I didn't define any. I just return a ModelAndView or #ResponseBody String from the controller methods. I guess there is a default view resolver (perhaps UrlBasedViewResolver) that is already configured. What is that? Where can I get info in the docs about this?
If I want to add other views like JsonView or XmlView (right now I use a Jsp that renders the data, but I read I can avoid this by passing model Objects directly to special views that will do this for me) how do I deal with this default view resolver?
The default is an automatically-registered InternalResourceViewResolver (UrlBasedViewResolver is an abstract superclass of this).
If you declare your own view resolver(s), then the default InternalResourceViewResolver will not be used. You can, if you, wish, simply redeclare it as an explicit bean. If there are multiple view resolvers, then they will be consulted in order until one of them returns a view object. However, because of the way that the servlet API works, InternalResourceViewResolver must always be the last view resolver in the chain.
If your controller method returns a View object directly, then no view resolver is necessary, and the view will be rendered directly. Similarly, if you use #ResponseBody, the view resolver is bypassed.
Related
Looking through the source for our applications, I found a common Spring MVC controller which display key and values of configuration copied and pasted several times. The class definitions are exactly the same, except for the RequestMapping value, since each application want to have this page available under different URLs.
I want to move this controller into a common library, and provide a default RequestMapping value.
#Controller
#RequestMapping (value="/property")
public class CommonPropertyController {
....
}
How would each application override this value if they want to use their own url pattern?
Looking at the source code I got an idea how to do it without having to go back to manual (pre-annotation) handler definition (which is also a way how to implement what you need).
Spring allows you to use property placeholder configurers in #RequestMapping values. So it is possible to use that fact and define #RequestMapping like:
#Controller
#RequestMapping("${routing.property.path}")
public class CommonPropertyController {
....
}
Then you can simply define PropertySourcesPlaceholderConfigurer with the right properties in your application context and you are good to go.
UPDATE You can also specify a default value using property placeholder if you want to have fallback mapping in case the property is not speciefied:
#RequestMapping("${routing.property.path:/property}")
Using ASP.NET MVC 3 I would like to render different "templates" (i.e. partial views or whatever you want to call them) to my views based upon the type of the view model (or a property on the view model) supplied at runtime.
I understand the out-of-the-box ASP.NET MVC functionality enables the addition of a UIHint attribute to a property or field on the viewmodel supplied to the view, but as far as I can tell this doesn't meet my needs as I won't know the template required until runtime.
AFAICT ASP.NET doesn't support generic viewmodels of type T either, so my idea is to take the ASP.NET MVC3 source code and write my own DisplayFor method that accepts a viewmodel instance (specified by it's abstract base type in the view), which resolves the actual type at runtime and uses this to find the template name by convention.
Does this sound feasible/worthwhile?
The view would look thus (note specification of base type, a concrete instance would be supplied at runtime):
#model MyViewModelBase
#{ DisplayFor(Model); }
Viewmodel:
public class MyMagicalViewModel : MyViewModelBase {}
Invocation of this view would look thus:
...
return View("MyView", MyMagicalViewModel);
...
...and this would return html corresponding to the MyMagicalViewModelPartialView.cshtml
Have you looked into Html.EditorFor?
I am using in my views (asp mvc3/razor cshtml) references to the Request object (eg, #Request.Params["Name"]). Do you think this is a very bad practice? Should I rewrite the value in the controller Request.Params ["Name"] to ViewBag.Name and then use it in the view (#ViewBag.Name)?
Best practice is to use a model class. An instance of the model class is created or updated in your controller. Then the controller displays a strongly-typed view.
So I'd avoid direct access to the request from the view as well as the use of the view bag.
Should I rewrite the value in the controller Request.Params ["Name"] to ViewBag.Name and then use it in the view (#ViewBag.Name)?
Yes. You will avoid runtime errors if "Name" does not exist.
The IDE will not warn you of the NullReferenceException about to be thrown with the following code.
#Request.Params["Fake"].ToString()
Of course, you'll have to be careful about ViewBag.Fake being null as well.
I like to use the viewbag to store things not related to the model, for example if I have a dropdown containing locations. I like to store only the id of the selected location on the model and the locations in the viewbag, since is not needed to create a contact. I think that's the purpose of the viewbag.
For me the model is a bag or properties used in business operations, for example if I have a customer creation view using a NewCustomerModel, I don't wanna pollute my model with things like a IList<CustomerType> AND a SelectedCustomerTypeId property. I just want the second since is the one imma use to create the customer.
I am creating a custom route by subclassing RouteBase. I have a dependency in there that I'd like to wire up with IoC. The method GetRouteData just takes HttpContext, but I want to add in my unit of work as well....somehow.
I am using StructureMap, but info on how you would do this with any IoC framework would be helpful.
Well, here is our solution. Many little details may be omitted but overall idea is here. This answer may be a kind of offtop to original question but it describes the general solution to the problem.
I'll try to explain the part that is responsible for plain custom HTML-pages that are created by users at runtime and therefore can't have their own Controller/Action. So the routes should be either somehow built at runtime or be "catch-all" with custom IRouteConstraint.
First of all, lets state some facts and requirements.
We have some data and some metadata about our pages stored in DB;
We don't want to generate a (hypothetically) whole million of routes for all of existing pages beforehand (i.e. on Application startup) because something can change during application and we don't want to tackle with pushing the changes to global RouteCollection;
So we do it this way:
1. PageController
Yes, special controller that is responsible for all our content pages. And there is the only action that is Display(int id) (actually we have a special ViewModel as param but I used an int id for simplicity.
The page with all its data is resolved by ID inside that Display() method. The method itself returns either ViewResult (strongly typed after PageViewModel) or NotFoundResult in case when page is not found.
2. Custom IRouteConstraint
We have to somewhere define if the URL user actually requested refers to one of our custom pages. For this we have a special IsPageConstraint that implements IRouteConstraint interface. In the Match() method of our constraint we just call our PageRepository to check whether there is a page that match our requested URL. We have our PageRepository injected by StructureMap. If we find the page then we add that "id" parameter (with the value) to the RouteData dictionary and it is automatically bound to PageController.Display(int id) by DefaultModelBinder.
But we need a RouteData parameter to check. Where we get that? Here comes...
3. Route mapping with "catch-all" parameter
Important note: this route is defined in the very end of route mappings list because it is very general, not specific. We check all our explicitly defined routes first and then check for a Page (that is easily changeable if needed).
We simply map our route like this:
routes.MapRoute("ContentPages",
"{*pagePath}",
new { controller = "Page", action = "Display" }
new { pagePath = new DependencyRouteConstraint<IsPageConstraint>() });
Stop! What is that DependencyRouteConstraint thing appeared in mapping? Well, thats what does the trick.
4. DependencyRouteConstraint<TConstraint> class
This is just another generic implementation of IRouteConstraint which takes the "real" IRouteConstraint (IsPageConstraint) and resolves it (the given TConstraint) only when Match() method called. It uses dependency injection so our IsPageConstraint instance has all actual dependencies injected!
Our DependencyRouteConstraint then just calls the dependentConstraint.Match() providing all the parameters thus just delegating actual "matching" to the "real" IRouteConstraint.
Note: this class actually has the dependency on ServiceLocator.
Summary
That way we have:
Our Route clear and clean;
The only class that has a dependency on Service Locator is DependencyRouteConstraint;
Any custom IRouteConstraint uses dependency injection whenever needed;
???
PROFIT!
Hope this helps.
So, the problem is:
Route must be defined beforehand, during Application startup
Route's responsibility is to map the incoming URL pattern to the right Controller/Action to perform some task on request. And visa versa - to generate links using that mapping data. Period. Everything else is "Single Responsibility Principle" violation which actually led to your problem.
But UoW dependencies (like NHibernate ISession, or EF ObjectContext) must be resolved at runtime.
And that is why I don't see the children of RouteBase class as a good place for some DB work dependency. It makes everything closely coupled and non-scalable. It is actually impossible to perform Dependency Injection.
From now (I guess there is some kind of already working system) you actually have just one more or less viable option that is:
To use Service Locator pattern: resolve your UoW instance right inside the GetRouteData method (use CommonServiceLocator backed by StructureMap IContainer). That is simple but not really nice thing because this way you get the dependency on static Service Locator itself in your Route.
With CSL you have to just call inside GetRouteData:
var uow = ServiceLocator.Current.GetService<IUnitOfWork>();
or with just StructureMap (without CSL facade):
var uow = ObjectFactory.GetInstance<IUnitOfWork>();
and you're done. Quick and dirty. And the keyword is "dirty" actually :)
Sure, there is much more flexible solution but it needs a few architectural changes. If you provide more details on exactly what data you get in your routes I can try to explain how we solved our Pages routing problem (using DI and custom IRouteConstraint).
Can I run a MVC 1 application using the MVC 2 assemblies without a hitch? I understand that some 3rd party tools have had stuff broken in MVC 2, but let's assume I'm not using those any other tools.
There have been breaking changes.
If you want a 1.0 project to run on 2.0, it does look for things in different places; so you'd have to migrate the application to 2.0..
Here are the changes that could possibly 'break' (current as of Preview 2):
Changes in Preview 2
Helpers now return an MvcHtmlString object
In order to take advantage of the new HTML-encoding expression syntax in ASP.NET 4, the return type of the HTML helpers is now MvcHtmlString instead of a string. Note that if you use ASP.NET MVC 2 and the new helpers with ASP.NET 3.5, you will not be able to take advantage of the HTML-encoding syntax; the new syntax is available only when you run ASP.NET MVC 2 on ASP.NET 4.
JsonResult now responds only to HTTP POST requests
In order to mitigate JSON hijacking attacks that have the potential for information disclosure, by default, the JsonResult class now responds only to HTTP POST requests. AJAX GET calls to action methods that return a JsonResult object should be changed to use POST instead. If necessary, you can override this behavior by setting the new JsonRequestBehavior property of JsonResult. For more information about the potential exploit, see the blog post JSON Hijacking on Phil Haack’s blog.
Model and ModelType property setters on ModelBindingContext are obsolete
A new settable ModelMetadata property has been added to the ModelBindingContext class. The new property encapsulates both the Model and the ModelType properties. Although the Model and ModelType properties are obsolete, for backward compatibility the property getters still work; they delegate to the ModelMetadata property to retrieve the value.
Changes in Preview 1
DefaultControllerFactory class changes break custom controller factories that derive from it
This change affects custom controller factories that derive from DefaultControllerFactory . The DefaultControllerFactory class was fixed by removing the RequestContext property and instead passing the request context instance to the protected virtual methods GetControllerInstance and GetControllerType.
Custom controller factories are often used to provide dependency injection for ASP.NET MVC applications.
To update the custom controller factories to support ASP.NET MVC 2, change the method signature or signatures to match the new signatures, and use the request context parameter instead of the property.
“Area” is a now a reserved route-value key
The string “area” in Route values now has special meaning in ASP.NET MVC, in the same way that “controller” and “action” do. One implication is that if HTML helpers are supplied with a route-value dictionary containing “area”, the helpers will no longer append “area” in the query string.
If you are using the Areas feature, make sure to not use {area} as part of your route URL.
Known Issues
The Add View dialog box throws a NullReferenceException when the Create strongly-typed view check box is checked, a View Content selection other than “Empty” is selected, and a View data class type name is specified for a type that does exist. When specifying a type name, either use the drop-down list to select the type name or type the fully-qualified type name. For types that do not exist, you must set View Content to “Empty”.
Check out this document. You'll learn about the breaking changes :
http://go.microsoft.com/fwlink/?LinkID=157072
One gotcha that has bitten me is that all the fields of a model are now always validated on a post.
The changes is described by Brad Wilson here.
Steve Anderson's blog post describes the issue and my favorite solution (using a custom validator).