Interfaces

Project

Pages Html

NuGet packageOwin.Framework.Pages.Html
GitHub sourceOwinFramework.Pages.Html

Home |  Readme

Component element reference | The OWIN FRamework

Component Elements

Component elements are the leaves of the tree structure that defines an HTML page. The components are responsible for writing the HTML to the response that is sent back to the browser. The HTML to write is defined in C# code.

Note that you do not have to create C# classes for every piece of content, that would be extremely time consuming and hard to manage. There are higher level facilities that create components for you, for example the template loader and template parser mechanism creates and configures a component for each template.

You will typically only write component classes to gain access to low level control over the HTML that is produced, for example to write custom headers into the <head></head> section of the page.

You can write components to simplify your chosen scheme for editing and presenting content. For example this website that documents the Owin Framework has a component that maps the page URL onto a template path allowing me to define one page that renders hundreds of different templates creating all of the pages on the website with very little code.

Components have the following features:

  • You can set an array of lambda functions into the HeadWriters property and they will be called during the rendering of the page head. This allows your component to write meta tags, links etc into the head of the page.
  • You can set an array of lambda functions into the ScriptWriters property and they will be called during the rendering of the javascript that is embedded into the page.
  • You can set an array of lambda functions into the StyleWriters property and they will be called during the rendering of the CSS that is embedded directly into the page.
  • You can set an array of lambda functions into the BodyWriters property and they will be called during the rendering of the page body. This is how things like template parsers configure components to render the template.
  • You can set an array of lambda functions into the InitializationWriters property and they will be called during the rendering of the page body after all of the components on the page have rendered their body content. This is a good place to write javascript code to execute initialization functions.
  • You can set an array of lambda functions into the CssRules property and they will be called during the rendering of the CSS assets. CSS assets maybe written into the page, or maybe written into an asset resource that is referenced by the page. This is determined by the asset deployment mechanism that is configured.
  • You can set an array of lambda functions into the JavascriptFunctions property and they will be called during the rendering of the Javascript. The Javascript can be rendered into the page head, or into a separate Javascript file depending on the asset deployment mechanism selected.
  • You can override the GetPageAreas method to indicate which areas of the page your component will write to. To improve page rendering efficiency the rendering engine will prune areas of the tree that contain no components that render to a specific portion of the page when that part of the page is being rendered.
  • You can override the GetCommentName method to provide a custom comment. These comments are written to the HTML when comments are turned on for debugging purposes.
  • You can override the WritePageArea method to output custom HTML in any area of the page where this component exists. Note that this is only guaranteed to be called for page areas returned by the GetPageAreas method, but it may also be called for areas that are not returned by the GetPageAreas if this component has children that render into those areas.
  • You can override the WriteStaticCss method to define what CSS gets written into asset resources associated with this component.
  • You can override the WriteStaticJavascript method to define what Javascript gets written into asset resources associated with this component.

Attribute Only Components

There are some situations in which you might want to define a component with no C# implementation because all of the required funcionality can be achieved through adding attributes to the component. For example:

[IsComponent("defaultStyles")]
[PartOf("application")]
[DeployedAs("content")]
[DeployCss("p", "font-size:11pt;")]
[DeployCss("h1", "font-size:16pt;")]
[DeployCss("h2", "font-size:14pt;")]
[DeployCss("h3", "font-size:12pt;")]
internal class DefaultStylesComponent { }

By decorating your component with the [IsComponent("component_name")] attribute you do not have to manually register each component with the Fluent Builder, instead you can ask the Fluent Builder to register everything in your assembly and it will use reflection to find all of the components.

The attributes that you can attach to a component are:

  • [IsComponent] identifies this class as a component
  • [DeployCss] css rules to include on any page that includes this component
  • [DeployFunction] a Javascript function to include on any page that has this component on it
  • [DeployedAs] specifies how Javascript and CSS should be deployed for this component
  • [NeedsComponent] identifies a dependent component
  • [NeedsData] identifies data that is required for this component
  • [PartOf] defines the package (namespace) for CSS and Javascript in this component
  • [RenderHtml] makes this component render localized HTML

Writing Custom HTML

To write custom HTML to anywhere on the page override the WritePageArea and GetPageAreas methods. For example:

[IsComponent("person")]
[NeedsData(typeof(Person))]
internal class PersonComponent : Component
{
    public PersonComponent(IComponentDependenciesFactory dependencies)
        : base(dependencies)
    {
    }
    public override IEnumerable<PageArea> GetPageAreas()
    {
        return new[] { PageArea.Body };
    }
    public override IWriteResult WritePageArea(IRenderContext context, PageArea pageArea)
    {
        if (pageArea == PageArea.Body)
        {
            var person = context.Data.Get<Person>();
            context.Html.WriteElementLine("p", person.Name);
        }
        return WriteResult.Continue();
    }
}

The WritePageArea method is called once for each component instance on the page when rendering the body of the page, but only once when rendering all other parts of the page. For example if I put the same component on my page 3 times, the body page area will be written 3 times (in each of the locations where I placed the component) but anything that this component renders in the page head, initialization area etc will only be written once.

If the component is inside of a region that repeats its content, this is the same as adding the component multiple times. Each time the region repeats the body area will be written again, but the page head will only be written once.

To be more specific, if your component writes to the <head></head> section of the page and the component is included multiple times on a page, then the WritePageArea method will only be called once for each page with the pageArea parameter set to PageArea.Head.

The WritePageArea method must return an instance of the IWriteResult interface. You can implement this interface yourself, or you can use static methods of the WriteResult class. The purpose of the IWriteResult is to let the rendering engine know if or when you are finished writing your HTML. The static methods of WriteResult work like this:

  • WriteResult.Continue() means I have finished writing all of my Html but other components should be run to contribute output as well. This is the most frequently used use case.
  • WriteResult.ResponseComplete() means I have finished writing all of my Html and the response is now complete so do not render output from any other controls. This is mostly usefull when wtiring to the page title and you do not want to concatenate the results of all component that write to the page title.
  • WriteResult.ContinueAsync(action) means that my Html takes a long time to produce, so kick it off in a background thread and continue running other components. If other components also return WriteResult.ContinueAsync(action) then these operations will continue in parallel on different threads. This option requires some special programmming to ensure that the asyncrhronously produced Html ends up in the correct sequence in the response. See section on this below.
  • WriteResult.WaitFor(task) means that I started a background task to write my Html. Please wait for this task to complete before sending the response back to the browser. In the meantime the other components on the page will continue processing and outputting their Html. See section below on asyncrhronously writing to the Html output stream.

Consuming Data in your Component

If your component needs data from a data provider it is better NOT to inject the data provider directly or look in the data catalog for it. Instead you should decorate your class with the [NeedsData()] attribute. If you do it this way there are several advantages:

  • If you place multiple components on the page that need the same data the data provider will only be called once.
  • If you place the component inside a repeater that repeats the data it needs then it will work as expected.
  • If the dependant data is available with multiple scopes then the page designer can choose which scope to apply and your component will receive the correct version of the data.

Here is an example of a component that uses data. This example will render Html for an address

    [IsComponent("address")]
    [NeedsData(typeof(Address))]
    internal class AddressComponent : Component
    {
        public AddressComponent(IComponentDependenciesFactory dependencies)
            : base(dependencies)
        {
        }
        public override IWriteResult WritePageArea(IRenderContext context, PageArea pageArea)
        {
            if (pageArea == PageArea.Body)
            {
                var address = context.Data.Get<Address>();
                context.Html.WriteElementLine("p", address.Street);
                context.Html.WriteElementLine("p", address.City);
                context.Html.WriteElementLine("p", address.ZipCode);
            }
            return WriteResult.Continue();
        }
    }

The [NeedsData()] attribute causes the requested type of data to be added to the IRenderContext where you can retrieve it by calling the Get<T>() method of its Data property.

Asyncronous HTML Output

The Pages Framework is designed to be extremely flexible and scaleable and therefore prvides a mecahnism to allow components to contribute asynchronously to the Html output. Here is an example:

public override IWriteResult WriteBodyArea(IRenderContext context)
{
    var html = context.Html;
    // Save this location in the output buffer
    var begining = html.CreateInsertionPoint();
    // Write a paragraph of text
    html.WriteElementLine("p", "This is paragraph 2");
    // Write a paragraph of text in a background thread
    var task = Task.Factory.StartNew(() =>
        {
            // Simulate a call to a service or database here
            Thread.Sleep(10);
            begining.WriteElementLine("p", "This is paragraph 1");
        });
    // Write a third paragraph of text
    html.WriteElementLine("p", "This is paragraph 3");
    return WriteResult.WaitFor(task);
}

In this example the resulting web page will contain the three paragraphs in numeric order even though paragraph 1 was written after the other two.

Components as Services

You can decorate your component's class with the [IsService] attribute to allow it to handle AJAX calls and Postbacks from the browser. There are a few supported use cases as follows:

  • Make your component class inherit from the Service class then implement the IComponent inferface.
  • Make your component class inherit from the Component class but also add a default public constructor that can be used to construct the class as a service.
  • Make your component class inherit from the Component class but and pass the optional factory method when registering with the fluent builder. The builder will use the factory to construct a second instance of your component to act as the service.
This technique is especially useful for modularizing your AJAX calls because you can write a component the will write Javascript functions to call the service, and also implement the service endpoints within the same class keeping these things close together. In this case adding the component to a website adds both the JavaScrpipt to call the service and the service endpoints.
Postbacks are a bit more tricky in that they have to re-render the page after handling the postback. Exactly how you do that depends a lot in the overall architecture of your solution.

Components in packages

You can create a reusable plug-in package. These packages can contain functionallity that can be added to a website with a single line of code. This technique is most often used to build open-source packages that are designed to be added to many websites and fulfill some common need, but you can also manage your own application using packages if you choose.

When you create a package, the package's Build method is called at startup. This is where the package creates all of the pages, layouts, components etc that comprise the package contents. This Build method is passed an instance of IFluentBuilder that can be used to build all of the package contents. The fluent builder has a namespace context that avoids namespace clashes with other packages and/or application code.

This is an example of using the fluent builder to build a component.

public class CmsEditorPackage: IPackage
{
    public CmsEditorPackage()
    {
        Name = "cms_editor";
        NamespaceName = "cmseditor";
    }
    IPackage IPackage.Build(IFluentBuilder fluentBuilder)
    {
		// ... some code omitted here for brevity
        var assetsComponent = fluentBuilder.BuildUpComponent(null)
            .Name("assets")
            .DeployIn(module)
            .DeployFunction(null, "initVue", null, script.ToString(), true)
            .RenderInitialization("cms-editor-init", "")
            .DeployLess(less.ToString())
			.Build();
        return this;
    }
}

Note that the Owin Framework NuGet package installs the xml documentation file alongside the dlls so you can get intellisense for the methods available in the fluent syntax.

Note that you can combine all of these techniques together, for example you can write a class that inherits from Component but also overrides methods of the base class, and uses the fluent builder syntax. This is illustrated in the example below:

public class LibrariesPackage : Framework.Runtime.Package
{
    public LibrariesPackage(IPackageDependenciesFactory dependencies)
        : base(dependencies)
    {
        Name = "libraries";
        NamespaceName = "libraries";
    }
	[DeployedAs(AssetDeployment.InPage)]
    private class LibraryComponent : Component
    {
        public string[] Urls { get; set; }
        public LibraryComponent(IComponentDependenciesFactory dependencies, params string[] urls)
            : base(dependencies)
        {
            PageAreas = new[] { PageArea.Head };
            Urls = urls;
        }
        public override IWriteResult WritePageArea(IRenderContext context, PageArea pageArea)
        {
            if (pageArea == PageArea.Head)
            {
                foreach (var url in Urls)
                    context.Html.WriteElementLine("script", string.Empty, "src", url);
            }
            return WriteResult.Continue();
        }
    }
    public override IPackage Build(IFluentBuilder builder)
    {
        builder.BuildUpComponent(
            new LibraryComponent(
                Dependencies.ComponentDependenciesFactory, 
                "https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"))
            .Name("jQuery1")
            .Build();
        return this;
    }
}