CustomMetadataProvired in Orchard

Topics: Customizing Orchard, Writing modules
Nov 1, 2011 at 3:37 PM

Hi

I created CustomMetadataProvider: DataAnnotationProvider to pass some custom metadata attibutes and behavior to the view.

After that I registered my custom provider in Global.asax on Application_Start() method and it works fine with common MVC application.

Problem is there is no Global.asax file in Orchard, no way to add my custom metadata provider to the application under Orchard.

Any ideas?

Coordinator
Nov 1, 2011 at 7:39 PM

What is this custom provider doing?

Nov 1, 2011 at 9:42 PM

Thanx for quick response

I have a Model with some data annotation attibutes and some methods which describe behavior of the Model.

namespace Traveler.Orchard.Models
{
    public class ViewModel<TModel> : DynamicObject
    {
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = Model.GetType().GetProperty(binder.Name).GetValue(Model, Type.EmptyTypes);
            return true;
        }

        [ShowForEdit(false)]
        [ShowForDisplay(false)]
        public TModel Model { get; set; }

        [BehaviorMetadata]
        public IEnumerable<KeyValuePair<Int32, String>> GetCustomerValues()
        {
            yield return;
            yield return;
            yield return;
        }

        [BehaviorMetadata]
        public ActionResult OnStatusChanged()
        {
            return MVC.Home.Index();
            //return InvokeAction.View<HomeController>(c => c.CustomerChanged(), null);
        }

        [BehaviorMetadata]
        public ActionResult Submit()
        {
            return MVC.Home.Index();
            //return InvokeAction.View<HomeController>(c => c.PlaceOrder(), null);
        }
    }
}

My custom metadata provider put this properties and behavior to ModelMetadata:

public class CustomMetadataProvider : DataAnnotationsModelMetadataProvider

    {
        protected override ModelMetadata CreateMetadata(System.Collections.Generic.IEnumerable<System.Attribute> attributes,
                                                                                               System.Type containerType,
                                                                                               System.Func<object> modelAccessor,
                                                                                               System.Type modelType,
                                                                                               string propertyName)
        {
            // Register base attributes
            var metadata = base.CreateMetadata(attributes, containerType, modelAccessor,
                                           modelType, propertyName);

            // BehaviurMetadataAttribute
            var behaviorMetadataAtt = attributes.OfType<BehaviorMetadataAttribute>().FirstOrDefault();
            if (behaviorMetadataAtt != null)
            {
                metadata.AdditionalValues.Add("BehaviorMetadata", behaviorMetadataAtt);
            }

            foreach (MethodInfo method in modelType.GetMethods())
            {
                foreach (ParameterInfo param in method.GetParameters())
                {

                }
                if (BehaviorMetadataAttribute.IsDefined(method, typeof(BehaviorMetadataAttribute)))
                {
                    metadata.AdditionalValues.Add(method.Name, new BehaviorMetadataProvider(method.Name, modelType));
                }
               
            }

            return metadata;
        }
    }

and then I invoke this methods inside my view:

if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("GetCustomerValues"))
                                {
                                    var provider = ViewData.ModelMetadata.AdditionalValues["GetCustomerValues"] as BehaviorMetadataProvider;
                                    IEnumerable<KeyValuePair<Int32, String>> output = provider.Execute()
                                                                            as IEnumerable<KeyValuePair<Int32, String>>;
                                    <td>
                                        <div class="getcustomervalues">
                                            @if (output != null)
                                            {
                                                @Html.DropDownList("customervalues", output.Select(item => new SelectListItem {Value = item.Key.ToString(), Text = item.Value.ToString()}),
                                                                        new Dictionary<string, object> {{"data-on-value-changed", "true"}})
                                            }
                                        </div>
                                    </td>     

To make this works I need to register CustomMetadatProvider as Current one in Global.asax.cs:

Global.asax.cs

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
            ModelMetadataProviders.Current = new CustomMetadataProvider();
        }

The problem is

i don't want make any changes in Orchard source code, just need to find way register MetadataProvider

on Application_Start()

Any ideas?

I don't want to make any changes in Orchard source code (Orchard.Web)

Coordinator
Nov 1, 2011 at 9:52 PM

You don't need to do this from a filter. The equivalent of a filter in Orchard, but that works down at the content level instead of at the request level is a handler. It should be easy to do the same kind of work from a handler, and stick the additional data on shapes rather than on ViewData (which is a little too global).

Nov 1, 2011 at 10:15 PM

I am new in Orchard, it is a little unclear for me. What do you mean "stick" data on shapes

Coordinator
Nov 1, 2011 at 10:25 PM

Shapes are a rough equivalent of view models in MVC, except that they are at the content part level, and they are dynamic objects. The typical cycle of a request is that a controller (often generic) creates a bunch of shapes, often by asking content part drivers to chime in. A few filters (such as the one for widgets) are also creating their own shapes and inserting them into zones on the layout (which is the outermost shape). Once all that is done, you get a tree of shapes that can be resolved into templates, which can do the actual rendering. Handlers can chime in at specific times in that cycle and modify the shapes on the fly. They can add new ones, remove others, or modify their properties. Because the objects are dynamic you can create your own custom properties on any shape you want.

Nov 1, 2011 at 11:20 PM

So, what way can I manage model metadata to put some methods and than execute it inside view

Coordinator
Nov 1, 2011 at 11:48 PM

I think I just told you. If you add properties to a shape, the template that will render that shape can access the new properties and do whatever it wants. The shape is the model for the template.