Search code examples
fubumvc

What is the proper way to register a display model and FubuControl?


Recently, I've begun updating a FubuMVC project from a pre-May/June 2010 version of the FubuMVC framework to the most recent version.

One of the biggest stumbling blocks I'm running into is how to configure FubuControls and the display models they use so that I can render those controls in a Site.Master page. The one FubuException error message I have trouble getting around is:

FubuMVC Error 2102:  
Unknown input type ResourceDisplay

thrown from method FindUniqueByInputType in FubuMVC.Core.Registration.Querying.ChainResolver line 69.

Here's my Global.asax.cs for the web project:

public class Global : FubuStructureMapApplication
    {
        protected override void InitializeStructureMap(IInitializationExpression ex)
        {
            Bootstrapper.ScanAndRegisterTypesForStructureMap(ex, new List<Registry>
                        {
                            new WebAppWebRegistry(),
                            new AppSettingProviderRegistry()
                        });
            setup_service_locator();
        }


        private static void setup_service_locator()
        {
            ServiceLocator.SetLocatorProvider(() => new StructureMapServiceLocator(ObjectFactory.Container));
        }

        public override FubuRegistry GetMyRegistry()
        {
            return new WebAppFubuRegistry();
        }
    }

Here's my FubuRegistry:

public class WebAppFubuRegistry : FubuRegistry
    {
        public WebAppFubuRegistry()
        {
            IncludeDiagnostics(true);

            Services(x => x.SetServiceIfNone<IWebAppSecurityContext, WebAppSecurityContext>());

            Applies.ToThisAssembly()
                .ToAssemblyContainingType<HomeController>();


            Actions
                .IncludeTypesNamed(x => x.EndsWith("Controller"));

            Routes
                .UrlPolicy<WebAppUrlPolicy>()
                .IgnoreControllerNamespaceEntirely()
                .ConstrainToHttpMethod(action => action.Method.Name.StartsWith("Perform"), "POST");


            Views
                .TryToAttach(x=>
                {
                    x.by<ViewAndActionInDifferentFolders>();
                    x.by_ViewModel_and_Namespace_and_MethodName();
                    x.by_ViewModel_and_Namespace();
                    x.by_ViewModel();
                });

            /* Irrelevant behaviors are added to configuration here */


            RegisterPartials(x => x.For<ResourceDisplay>().Use<ResourceTracker>());
            //this.UseDefaultHtmlConventions();

            //HomeIs<HomeController>(x => x.Index());
        }
    }

More information and update:

Part of the problem I discovered was that when I was updating the project I changed:

public class SiteMasterView : FubuMasterPage<EmptyModel>, IFubuPage<EmptyModel>{ }

to

public class SiteMasterView : FubuMasterPage

which meant that the extensions function RenderPartial<TViewModel>(this IFubuPage<TViewModel> viewPage) was no longer usable from SiteMasterView.

Here's the old and invalid RenderPartial<TViewModel> Call in Site.Master:

<%= this.RenderPartial().Using<ResourceTracker>().For(Get<UserResources>().Resources)%>

So, I think at this point I considered trying to create my own RenderPartial extension function that would work with FubuMasterPage (instead of just with IFubuPage<EmptyModel>). But I figured I might as well update my usage of the FubuFramework and use the void Partial<TInputModel>(this IFubuPage page) where TInputModel : class; extension function instead of the older more verbose version.

The new call:

<% this.Partial<ResourceDisplay>(); %>

That's when I began running into the error emitted by the ChainResolver, and I tried a variety of solutions, including creating an implementation of IFubuCommand that generated a ResourceDisplay. Also I attempted to register the partial explicitly in my FubuRegistry. But I've had no luck in getting past the ChainResolver.


What is the preferred way to register FubuControls and their view models with the FubuFramework?

How do I fix the error mentioned above?


Solution

  • Mark,

    First: I think what you're looking for is RenderPartialFor<TViewModel>. That should still work, even on master pages.

    Second: Let me address the confusion between RenderPartial* and Partial.

    This is a known issue and causes lots of confusion for people. RenderPartial is quite different from Partial. I know it's confusing, I'm sorry. We're planning on fixing this soon, in the run-up to FubuMVC 1.0.

    RenderPartial does not execute an behavior chain/action. It's very similar to how UserControls worked in ASP.NET WebForms. It's a simple rendering of a UserControl.

    Partial, however, does a "partial invoke" of a behavior chain/action. This allows you to invoke a different action in the context of the current action. Since it is a partial invocation, not all of the behaviors are executed. For example, if you had a TransactionalBehavior that managed a DB transaction for the life of an HTTP request, and then you did a Partial() invoke of a behavior chain, it would not spawn a new DB transaction.

    Behaviors are coded with the knowledge that they decide whether it is appropriate for them to execute during a partial invoke.

    So you need to decide whether you want simple content using RenderPartial (like a simple header/footer or reusable chunk of static HTML), or whether you need to do some logic in an action to produce a model that is bound to a partial view using the Partial method.

    In the case of Partial, it would still use an ASPX as its view, except that ASPX should not have full HTML (no begin <html> tag or body tag, for example) because it will always be loaded into the middle of a larger HTML document from another view. The partial ASPX should not usually use master pages, etc.

    Any action can be invoked via Partial and is identified by its input model or a lamda reflecting its ControllerType.ActionMethod. However, I recommend that you do not execute any old action using Partial, but have actions that are meant to be invoked partially.

    By default, all actions will receive a route via FubuMVC. For partial actions, you may not want them to be routable (i.e. they can only be called in the context of another action on the server side). To designate this, you need to apply a convention to let Fubu know not to route these actions. By default, FubuMVC has a built-in convention to not route any action with the [FubuPartial] attribute. You don't have to use this convention if you don't want to, but it is convenient since it's built-in automatically for you.

    A reason you may want to have a partial action have a route is if you want to use the jQuery $.load() AJAX method to load partial HTML content on the server from the web browser (i.e. something changes on the page and you want to refresh some content from the server without reloading the entire page/screen).