Extending

Views

Any view can be overridden within a theme. KIT will use the following order when selecting the view to use:

  1. Views/{Module}/{Controller}/{Action}.cshtml
  2. Views/{Module}/Shared/{Action}.cshtml
  3. Views/Shared/{Action}.cshtml

Controllers

To override a particular controller's action you must make sure the routing attribute must have a higher order than the route it is trying to override.

Services

To override a particular service you must declare the new service within the module's “HostingStartup.cs” file.

Models

CustomT : T

If you would like to use a single table (using ClassMapping and not SubclassMapping) then you must add Polymorphism(PolymorphismType.Explicit) to the mapping for CustomT to avoid Session.Query<T>().All() returning duplicates. However you won’t be able to cast T to CustomT.

To overcome the casting issue you may wish to add generic methods to your services. That way you can return the concrete type which doesn’t require casting.

If you map the sub type to its own table (using a SubclassMapping) then you can cast T to CustomT but you have to use multiple tables (without adding a discriminator to T which requires modifying the core assembly).

CustomT : T : IT (interface)

An interface is completely optional and doesn’t really offer any real benefits since CustomT must inherit T anyway. Therefore the same restrictions above apply here. However the interface may exist in an abstractions library.

If you decide to use this approach then you must provide NHibernate with a default type when mapping any properties of type IT. Therefore any references would say `ManyToOne<T>(x => x.SomeProperty)`. If you don't have access to the concrete type then you could add a convention or preferably you should change the entity name in the class mapping by saying `EntityName(typeof(IT).FullName)`. This effectively switches the functionality so now properties of type IT are automatically mapped but properties of type T would need to say `ManyToOne<IT>(x => x.SomeProperty)` or use a convention. This has the advantage that it allows you to add attributes where you don't have access to the concrete type.

Attributes

Attributes is another way you can extend an entity model. For example, say you have the following class:

[Attributes<IUser>()]
public class UserAttributes {
    [Required]
    public string CustomField { get; set; }
}

This adds the "CustomField" to the attributes collection against an entity. You could then retrieve the attributes by saying:

var foo = (await _dataContext.Repositor<IUser>().GetAsync(1)).Attributes.Get<string>("CustomField");

Placeholders

Sometimes overriding an entire view is not the best idea. If the overridden view was changed in the future then you may lose some functionality. To overcome this problem most of the modules have placeholders inside the view where you can add custom code. To inject your own custom code within the placeholder you need to register it inside the HTML Manager. Here's an example which adds the text "Custom code" where the placeholder named "MyPlaceholder" exists:

public class MyPlaceholderActionFilter : ActionFilterAttribute {
    private readonly IHtmlManager _htmlManager;

    public MyPlaceholderActionFilter(IHtmlManager htmlManager) {
        _htmlManager = htmlManager;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        if (!filterContext.HttpContext.Request.IsAjaxRequest() && !filterContext.HttpContext.IsChildAction())
            _htmlManager.RegisterHtml("MyPlaceholder", new HtmlString("Custom code"));

        base.OnActionExecuted(filterContext);
    }
}

Note: Placeholders are typically named "{Module}_{Controller}_{Action}_{Position}" , where the position is a unique name to describe the position in the view.

You must then register the action filter within the container via the "HostingStartup.cs" file:

services.Configure<MvcOptions>(options => options.Filters.Add(typeof(MyPlaceholderActionFilter)));

This will make the action filter run for every request, therefore if the filter is quite heavy (for example if it accesses the database) then you should filter when you would like to register the placeholder. The above example will only register the placeholder for non-ajax requests and when it is not within a child action, however you may wish to limit it to only run for a particular action.