Best asp.net-mvc-3 questions in February 2011

ASP.NET MVC 3 - Partial vs Display Template vs Editor Template

26 votes

Hi Guys,

So, the title should speak for itself.

To create re-usable components in ASP.NET MVC, we have 3 options (could be others i haven't mentioned):

Partial View:

@Html.Partial(Model.Foo, "SomePartial")

Custom Editor Template:

@Html.EditorFor(model => model.Foo)

Custom Display Template:

@Html.DisplayFor(model => model.Foo)

In terms of the actual View/HTML, all three implementations are identical:

@model WebApplications.Models.FooObject

<!-- Bunch of HTML -->

So, my question is - when/how do you decide which one of the three to use?

What i'm really looking for is a list of questions to ask yourself before creating one, for which the answers can be used to decide on which template to use.

Here's the 2 things i have found better with EditorFor/DisplayFor:

  1. They respect model hierarchies when rendering HTML helpers (e.g if you have a "Bar" object on your "Foo" model, the HTML elements for "Bar" will be rendered with "Foo.Bar.ElementName", whilst a partial will have "ElementName").

  2. More robust, e.g if you had a List<T> of something in your ViewModel, you could use @Html.DisplayFor(model => model.CollectionOfFoo), and MVC is smart enough to see it's a collection and render out the single display for each item (as opposed to a Partial, which would require an explicit for loop).

I've also heard DisplayFor renders a "read-only" template, but i don't understand that - couldn't i throw a form on there?

Can someone tell me some other reasons? Is there a list/article somewhere comparing the three?

EditorFor vs DisplayFor is simple. The semantics of the methods is to generate edit/insert and display/read only views (respectively). Use DisplayFor when displaying data (i.e. when you generate divs and spans that contain the model values). Use EditorFor when editing/inserting data (i.e. when you generate input tags inside a form).

The above methods are model-centric. This means that they will take the model metadata into account (for example you could annotate your model class with [UIHintAttribute] or [DisplayAttribute] and this would influence which template gets chosen to generate the UI for the model. They are also usually used for data models (i.e. models that represent rows in a database, etc)

On the other hand Partial is view-centric in that you are mostly concerned with choosing the correct partial view. The view doesn't necessarily need a model to function correctly. It can just have a common set of markup that gets reused throughout the site. Of course often times you want to affect the behavior of this partial in which case you might want to pass in an appropriate view model.

You did not ask about @Html.Action which also deserves a mention here. You could think of it as a more powerful version of Partial in that it executes a controller child action and then renders a view (which is usually a partial view). This is important because the child action can execute additional business logic that does not belong in a partial view. For example it could represent a shopping cart component. The reason to use it is to avoid performing the shopping cart-related work in every controller in your application.

Ultimately the choice depends on what is it that you are modelling in your application. Also remember that you can mix and match. For example you could have a partial view that calls the EditorFor helper. It really depends on what your application is and how to factor it to encourage maximum code reuse while avoiding repetition.

How to implement a caching model without violating MVC pattern?

12 votes

Hi Guys,

I have an ASP.NET MVC 3 (Razor) Web Application, with a particular page which is highly database intensive, and user experience is of the upmost priority.

Thus, i am introducing caching on this particular page.

I'm trying to figure out a way to implement this caching pattern whilst keeping my controller thin, like it currently is without caching:

public PartialViewResult GetLocationStuff(SearchPreferences searchPreferences)
{
   var results = _locationService.FindStuffByCriteria(searchPreferences);
   return PartialView("SearchResults", results);
}

As you can see, the controller is very thin, as it should be. It doesn't care about how/where it is getting it's info from - that is the job of the service.

A couple of notes on the flow of control:

  1. Controllers get DI'ed a particular Service, depending on it's area. In this example, this controller get's a LocationService
  2. Services call through to an IQueryable<T> Repository and materialize results into T or ICollection<T>.

How i want to implement caching:

  • I can't use Output Caching - for a few reasons. First of all, this action method is invoked from the client-side (jQuery/AJAX), via [HttpPost], which according to HTTP standards should not be cached as a request. Secondly, i don't want to cache purely based on the HTTP request arguments - the cache logic is a lot more complicated than that - there is actually two-level caching going on.
  • As i hint to above, i need to use regular data-caching, e.g Cache["somekey"] = someObj;.
  • I don't want to implement a generic caching mechanism where all calls via the service go through the cache first - i only want caching on this particular action method.

First thought's would tell me to create another service (which inherits LocationService), and provide the caching workflow there (check cache first, if not there call db, add to cache, return result).

That has two problems:

  1. The services are basic Class Libraries - no references to anything extra. I would need to add a reference to System.Web here.
  2. I would have to access the HTTP Context outside of the web application, which is considered bad practice, not only for testability, but in general - right?

I also thought about using the Models folder in the Web Application (which i currently use only for ViewModels), but having a cache service in a models folder just doesn't sound right.

So - any ideas? Is there a MVC-specific thing (like Action Filter's, for example) i can use here?

General advice/tips would be greatly appreciated.

My answer is based on the assumption that your services implement an interface, for example the type of _locationService is actually ILocationService but is injected with a concrete LocationService. Create a CachingLocationService that implements the ILocationService interface and change your container configuration to inject that caching version of the service to this controller. The CachingLocationService would itself have a dependecy on ILocationService which would be injected with the original LocationService class. It would use this to execute the real business logic and concern itself only with pulling and pushing from cache.

You don't need to create CachingLocationService in the same assembly as the original LocationService. It could be in your web assembly. However, personally I'd put it in the original assembly and add the new reference.

As for adding a dependency on HttpContext; you can remove this by taking a dependency on

Func<HttpContextBase> 

and injecting this at runtime with something like

() => HttpContext.Current

Then in your tests you can mock HttpContextBase, but you may have trouble mocking the Cache object without using something like TypeMock.


Edit: On further reading up on the .NET 4 System.Runtime.Caching namespace, your CachingLocationService should take a dependency on ObjectCache. This is the abstract base class for cache implementations. You could then inject that with System.Runtime.Caching.MemoryCache.Default, for instance.

ViewBag vs ViewData performance difference in MVC?

9 votes

I know that ViewData and ViewBag both use the same backing data and that neither are as good as using strongly typed models in most cases. However when choosing between the two is the dynamic nature of ViewBag slower than using ViewData?

Okay - my initial answer basically said 'no' - time for a bit of a u-turn.

It should be 'no' in a perfect dynamic world - but upon closer inspection it would appear that there will either be no difference (accounting for JIT magic) or it might be ever-so-slightly slower, although not enough to warrant not using it (I certainly am).

In theory if properly implemented, the ViewBag would ultimately outperform the use of the ViewData dictionary because the binding of the expressions (e.g. ViewBag.Foo) is very well cached across the different CallSites that the compiler will generate (reflect a method that does a read or write to the ViewBag and you'll see what I mean).

The caching layers of the DLR are well documented (if a little difficult to understand once you get in depth) but basically the runtime does its best to 'remember' where a given value instance is once its bound it - for example via a Set or Get statement.

BUT The caching, its use and effectiveness, is entirely dependent upon the underlying implementations of classes/interfaces such as DynamicObject, IDynamicMetaObjectProvider etc; as well as the end-result of the Get/Set expression binding.

In the case of the MVC internal DynamicViewDataDictionary class - it ultimately ends up binding to this:

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
  result = this.ViewData[binder.Name];
  return true;
}

For var a = ViewBag.Foo

And

public override bool TrySetMember(SetMemberBinder binder, object value)
{
  this.ViewData[binder.Name] = value;
  return true;
}

For ViewBag.Foo = Bar;

In other words - the statements are effectively being rewritten to wrappers around the dictionary indexer.

Because of that, there's certainly no way it could be faster than doing it yourself.

Were ViewData to feed off of ViewBag, instead of the other way around, and had ViewBag then been implemented with even something like ExpandoObject, then it might be a different story - as the dynamic implementation of ExpandoObject is much more intelligent and the caching rules it employs allow for some pretty cool runtime optimisations.

In Conclusion

(thanks to Shawn McLean for suggesting one was needed!)

ViewBag will be slower than ViewData; but probably not enough to warrant concern.

My logging framework is tied to my application forever!

8 votes

Ok, so I'm looking at NLog. Based on the usage, my application would be tied to the logging framework. How do I overcome this?

Also, when using NLog, I have to write too much monkey-code for every class I'm using this framework on. Is it a good practice to make one static class and access it from anywhere in my application?

example:

//the monkey code
private static Logger logger = LogManager.GetCurrentClassLogger();

//the coupling.
logger.Log(/*...*/);

  1. Create your own logging interface:

    public interface IMyOwnLogger {
        void Log(string message);
    }
    
  2. Create implementation:

    public class NLogLogger : IMyOwnLogger {
        void Log(string message) {
            StackFrame frame = new StackFrame(1, false);
            Logger logger = LogManager.GetLogger(frame.GetMethod().DeclaringType.FullName);
            logger.Log(/*...*/);
        }
    }
    
  3. Bind IMyOwnLogger to NLogLogger in your IOC container.

  4. Inject where needed (or use IOC.Get<IMyOwnLogger>()).

EDIT:

Idsa made a comment about loosing calling class. Remember you can always use stack trace:

var method = (new StackTrace()).GetFrame(1).GetMethod()

and extract calling class from there.

EDIT:

This is how GetCurrentClassLogger in NLog looks like, so using StackTrace in our class doesn't create additional overhead:

[MethodImpl(MethodImplOptions.NoInlining)]
public static Logger GetCurrentClassLogger()
{
    #if SILVERLIGHT
    StackFrame frame = new StackTrace().GetFrame(1);
    #else
    StackFrame frame = new StackFrame(1, false);
    #endif

    return globalFactory.GetLogger(frame.GetMethod().DeclaringType.FullName);
}

How does ASP.NET MVC know how to fill your model to feed your Controller's Action? Does it involve reflection?

8 votes

Having defined a Model

public class HomeModel {
    [Required]
    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Required]
    [Display(Name = "Surname")]
    public string Surname { get; set; }
}

and having the following Controller

public class HomeController : Controller {
    [HttpPost]
    public ActionResult Index(HomeModel model) {
        return View(model);
    }

    public ActionResult Index() {

        return View();
    }
}

by some "magic" mechanism HomeModel model gets filled up with values by ASP.NET MVC. Does anyone know how?

From some rudimentary tests, it seems it will look at the POST response and try to match the response objects name with your Model's properties. But to do that I guess it must use reflection? Isn't that inheritably slow?

Thanks

Yes, you are talking about the magic ModelBinder.

ModelBinder is responsible for creating a Model and hydrating it with values from the form post-back and performing validation which its result will appear in ModelState.

Default implementation is DefaultModelBinder but you can plug-in your own.

Is it possible, in MVC3, to have the same controller name in different areas ?

8 votes

In MVC3, I have the following areas:

  • Mobile
  • Sandbox

Then i route maps like this:

    context.MapRoute(
        "Sandbox_default",
        "Sandbox/{controller}/{action}/{id}",
        new { controller = "SandboxHome", action = "Index", id = UrlParameter.Optional }

and

    context.MapRoute(
        "Mobile_default",
        "Mobile/{controller}/{action}/{id}",
        new { controller = "MobileHome", action = "Index", id = UrlParameter.Optional }
    );

The problem is this gives urls like:

http://localhost:58784/Mobile/MobileHome

and

http://localhost:58784/Sandbox/SandboxHome

But I want it like this:

http://localhost:58784/Mobile/Home
http://localhost:58784/Sandbox/Home

The problem is when I rename the SandboxHome-Controller to Home, and the MobileHome-Controller to Home, which would give the desired URLs, it won't compile, saying it has two classes for HomeController.

How can I have the same controller name in different areas ?

Yes.

As explained by this blog post: http://haacked.com/archive/2010/01/12/ambiguous-controller-names.aspx

You need to specify the namespace on the default route in global ASAX to prevent conflicts.

//Map routes for the main site. This specifies a namespace so that areas can have controllers with the same name
routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        new[]{"MyProject.Web.Controllers"}
 );

As long as you keep the Area controllers within their own namespaces. This will work.

Problem running MVC3 app in IIS 7

7 votes

I am having a problem getting a MVC 3 project running in IIS7 on a computer running Windows 7 Home-64 bit. Here is what I did.

  1. Installed IIS 7.
  2. Accessed the server and got the IIS welcome page.
  3. Created a directory named d:\MySite and copied the MVC application to it. (The MVC app is just the standard app that is created when you create a new MVC3 project in visual studio. It just displays a home page and an account logon page. It runs fine inside the Visual Studio development server and I also copied it out to my hosting site and it works fine there)
  4. Started IIS management console.
  5. Stopped the default site.
  6. Added a new site named "MySite" with a physical directory of "d:\Mysite"
  7. Changed the application pool named MySite to use .Net Framework 4.0, Integrated pipeline

When I access the site in the browser I get a list of the files in the d:\MySite directory. It is as if IIS is not recognizing the contents of d:\MySite as an MVC application.

What do I need to do to resolve this?

As requested, here is the web.config:

    <?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->

<configuration>
  <connectionStrings>
    <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>

  <appSettings>
    <add key="ClientValidationEnabled" value="true"/> 
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/> 
  </appSettings>

  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>

    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
             enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
             maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
             applicationName="/" />
      </providers>
    </membership>

    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
      </providers>
    </profile>

    <roleManager enabled="false">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/" />
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
      </providers>
    </roleManager>

    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages"/>
      </namespaces>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>

  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

I posted this question on "ServerFault" as well and got a resolution to the issue here.

The answer is:

Since IIS was installed after .NET 4, you likely need to run the aspnet_regiis.exe tool to register all the .NET 4 stuff with IIS.

ASP.NET MVC 3 - Ajax.BeginForm vs jQuery Form Plugin

6 votes

I'm starting a new ASP.NET MVC 3 project and am going to implement some screens that are read only by default but allow the user to edit information by clicking on an Edit button. I want these screens to be AJAXed. I have previously used the jQuery Form Plugin to implement similar screens on an ASP.NET MVC 2 project.

I've just discovered the existence of Ajax.BeginForm() and was wondering whether I should use that since it is built in, instead of using the jQuery Form Plugin. I've done a Google search on the difference between the two techniques but couldn't find anything.

What I would like to know is which one (or a different one altogether) should I use with ASP.NET MVC 3.

What are the best practices and libraries for implementing AJAX forms in ASP.NET MVC?

What are the strengths and weaknesses of Ajax.BeginForm vs the jQuery Form Plugin?

I would use the Form Plugin.

In MVC 3, the Ajax helper is basically implemented using jQuery Ajax. (See Brad's Wilsons post on unobtrusive Ajax in MVC 3.) The upside to using the Form Plugin is that you'll have more control over your pages and you don't have to use the clunky BeginForm API.

MVC site search functionality

6 votes

i need a simple site search functionality for my mvc app. some of the pages are static and some dynamic (like news articles that are entered in cms). I would like the search to handle both. is this product any good? http://www.sitesearchasp.net any other?

@stephbu - Thank you for the mention.

If you choose to use arachnode.net, you have the choice of either Lucene.NET or SQL Full-text Indexing.

There are some 'head-scratchers' with Lucene.NET, especially when establishing concurrent read/write/search scenarios, but as a static reflection of content it works very well.

If you want something that is free, and turn-key, try Solr(.Net) or Microsoft Search Server.

http://www.microsoft.com/enterprisesearch/en/us/search-server-express.aspx (this was free last I looked at it...)

Thanks! Mike

Partial Page Caching and VaryByParam in ASP.NET MVC 3

6 votes

I'm attempting to use the new partial page caching available in ASP.NET MVC 3. In my view, I'm using:

<% Html.RenderAction("RenderContent", Model); %>

Which calls the controller method:

[Authorize]
[OutputCache(Duration = 6000, VaryByParam = "*", VaryByCustom = "browser")]
public ActionResult RenderContent(Content content)
{
   return PartialView(content);
}

Note that both the original view and the partial view are using the same view model.

The problem is that VaryByParam doesn't work - RenderContent() always returns the same cached HTML no matter what view model is passed to it. Is there something about VaryByParam that I don't understand?

I think I figured it out. It looks like the issue is that VaryByParam, when the input parameter is an object, uses ToString() on that object to determine it's uniqueness. So this leaves two options:

  1. Overriding ToString() to provide a unique identifier.
  2. Passing a unique identifier as an additional parameter:

    <% Html.RenderAction("RenderContent", Model, Model.Id); %>
    
    [Authorize]
    [OutputCache(Duration = 6000, VaryByParam = "id", VaryByCustom = "browser")]
    public ActionResult RenderContent(Content content, string id)
    {
       return PartialView(content);
    }