Ask Kathleen

Kathleen Dollard Digs Deeper into ASP.NET MVC 3

The second part of this month's Ask Kathleen column on MVC 3 looks at dependency injection and extending parts of the framework. Part 2 of 2.

Kathleen Dollard continues her exploration of ASP.NET MVC 3 with a look at improved Dependency Inversion and the Custom Model Binder in the updated framework. You can read Part 1 of this column here.

Dependency Inversion
ASP.NET MVC offered many extension points in earlier versions of the framework. MVC 3 adds a couple of new extensibility points and provides better dependency inversion through the IDependencyResolver interface. This interface can be implemented for individual Dependency Inversion (DI) tools. It presents a consistent interface to the application.

Improvements to view helpers (Html.Helpers) and to the scaffolding templates also appear in MVC 3. The best way to explore these and other features is experimentation with the new version. MVC 3 also introduces NuGet -- a free, open source package manager -- as a common way of distributing MVC components, although it's not limited to MVC.

Q: When Microsoft added dependency inversion to the ASP.NET MVC 3 framework, why didn't the developers use the Managed Extensibility Framework (MEF)? Is the MEF dead?

A: One aspect of the extensibility features in MVC 3 is use of a dependency resolver, similar to a service locator or an Inversion of Control (IoC) container. The dependency resolver is optional and generalized; you can create a wrapper for the IoC container of your choice. I can't speak for Microsoft, but I'll tell you why I think this was a good decision.

The MEF is a good dependency-inversion tool and works well as a dependency resolver for MVC. The MEF is not dead, and the MEF team has been doing interesting things, which you can see in the ongoing CodePlex drops.

The .NET Framework 4 version of the MEF was version 1. A lot of small improvements appear in the existing and upcoming CodePlex drops, including a better debugging story, ExportFactory in the .NET version, and improvements to the internal codebase. The big feature that's missing is the ability to instantiate a part from an open generic. That functionality should appear in drops around March. The MEF is alive and well and I'm excited about the upcoming version.

So, why doesn't MVC use it? People are familiar with the many well-established IoC containers embedded in existing systems. It would've been an enormous mistake if the ASP.NET MVC team had thumbed their noses at this installed base by forcing MEF usage, and introducing the unnecessary complexity of multiple IoC containers. It's consistent with the MVC philosophy of deep extensibility that you can use any dependency-injection tool. I also think that in version 1, the MEF probably didn't feel quite ready when the team began ASP.NET MVC 3 planning (no ExportProvider in the .NET 4 MEF, for example). The teams are talking to each other, so this isn't a "left hand doesn't know what the right hand is doing" scenario.

The MEF offers a couple of benefits for MVC-dependency resolution. Directory discovery is a simple approach appropriate on a server that avoids one more point of configuration. Catalogs shared across nested composition containers ensure discovery happens only once while retaining shared parts reused only across the session. You can create a prioritization system -- and even add prioritization to an existing system -- through a custom catalog and export composition metadata. The lack of generic instantiation in this version of the MEF is not a hindrance in MVC because MVC generally uses a factory pattern, allowing you to easily instantiate concrete type-specific generics outside the MEF infrastructure.

A unique feature of the MEF is that retrieval is not type-based but export part-based, and parts can be Lazy and contain export metadata. This is extremely helpful because it means factories and providers have whatever extra information you need to make decisions before any return value is instantiated.

Q: I'm using MVC and I have a flag that indicates whether the form that I'm storing as a hidden input element is dirty. A setting via JavaScript indicates whether my records are dirty. I bring the form data back to the server and let MVC build the parameter object. The parameter object contains the dirty flag, but it's always set to dirty. Can you tell me why this is happening and how to fix it?

A: I assume you're setting the dirty flag in the property setters of the object. Because the default model binding uses those property setters, they're setting your dirty flag. There are a few approaches to solving this problem. I think the simplest and most elegant approach leverages the MVC load process using a "model binder."

Custom Model Binder
The MVC infrastructure, including the data-load process, is an excellent example of the power of composition, inversion and the Open/Closed principle. The Open/Closed principle says that objects should be open for extension but closed to modification. You want all the great load behavior you get now, except you want to fix a specific problem. You can do this with a targeted extension that drops your logic into just the right place as a custom model binder. Model binders will probably be the first place most programmers will extend the ASP.NET MVC framework.

Internally, the MVC framework uses reflection to determine the parameters on your action methods. It then asks a model binder to load the object via the BindModel method, which is the only method on the IModelBinder interface. Model binders are specific to the types they bind, and the MVC framework restricts the search to appropriate binders.

Prior to ASP.NET MVC 3, you explicitly registered the binders along with the types they bound. This story changed a bit in MVC 3, which introduced a ModelBinderProvider. The framework collects all the ModelBinderProviders -- both statically registered and returned from the dependency resolver -- and asks each provider to return a model binder. As soon as a provider returns a non-null value, the search process terminates and that ModelBinder is used. Providers from the dependency resolver are evaluated first, followed by statically registered binders. If you don't supply a provider, and there's a registered ModelBinder, it will be used. Regardless of how the model binder is retrieved, it has the single job of supplying a fully populated instance of the type.

Inheriting from the default model binder lets you delegate the bulk of the work to the existing framework class. The BindModel method received the controller context and the binding context as parameters. The controller context provides access to the session environment and the request. The binding context provides information about the parameter requested via the model metadata system of MVC. You'll generally work with the model name of the binding context:

public class BizObjectModelBinder : DefaultModelBinder
{
public override objectBindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var name = bindingContext.ModelName;

Calling the base class BindModel method retrieves the requested type loaded with the majority of its data:

  var bizObject = base.BindModel(controllerContext, bindingContext) as IBizObject;

While you could directly access the request object, it would require a lot of ugly parsing and break the single-responsibility principle. This class should know about the translation of data -- from the abstraction of the request and environment to the abstraction of the target object -- and nothing else. MVC wraps request access, and potentially access to other information, in "value providers." You request a piece of information and get a ValueProviderResult. You can ignore how the value was actually retrieved. If the value isn't available, the value provider returns null. Depending on your circumstance, a missing value might represent a serious problem, but I'm assuming it's not a problem and continuing:

    ValueProviderResult instanceStateProvider = 
bindingContext.ValueProvider.GetValue("InstanceState");
if (instanceStateProvider != null)
{
var rawValue = instanceStateProvider.AttemptedValue.ToString();
var intValue = int.Parse(rawValue);
InstanceState instanceState = (InstanceState)intValue;
bizObject.InstanceState = instanceState;
}
returnbizObject;
}
}

This code assumes that you're either retrieving a specific type (in place of bizObject) or retrieving objects based on an interface that includes the InstanceState property, allowing it to be set. I'm using a flagged enum for instance state to combine indications of whether the record was modified, created or deleted to show how to manage a value more complex than a Boolean.

In addition to fixing serialization issues, model binders can isolate the client-side and server-side abstractions. For example, the popular jqGrid has short and vague argument names, such as "sidx" for the sort column -- which incidentally will generally not be an actual index but rather the name of the sort column. How confusing is that! You can use a model binder to translate to a better name and have a single point of change should you move to a different grid.

The compositional approach of ASP.NET MVC extends to value providers. You can write your own value providers, which can offer default information for complex objects (simple defaults can be included in the parameter list of your action method); interpret information offered in the request; and retrieve information from sources entirely outside the request and session, such as a value from Web config or a calculated value.

Q: Every time I start debugging an ASP.NET MVC app through the development server and I have a Razor view open and selected in Visual Studio, I get this error:

Unable to cast object of type 'ASP._Page_Views_Shared_SimpleSearch_cshtml' to type 'System.Web.IHttpHandler'.

If I close the Web page and then select a tab in Visual Studio that doesn't contain a Razor view, I can start the application.

A: If the Start Action for your Web project is Current Page, Visual Studio will try to open the Razor file as an HTML file. It will then fail because your file isn't all valid HTML. You can check this setting on the Web tab of your Web project's property dialog. Set the Start Action to Specific Page with an empty page.

There are two web.config files in an MVC project. One is located in the project root and the other is located in the Views folder. The web.config located in the Views folder should have this setting:

<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>

This appSettings entry also prohibits someone from navigating to your site with a URL such as SiteName.com/Views/Home/Index.cshtml. While the Razor content would probably create an error and no data would be displayed, it might expose details about your server that you don't want in a hacker's hands.

After you make these two changes, your ASP.NET MVC project should start normally.

Thanks to Marcin Dobosz at Microsoft for help with this problem.


About the Author

Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube