Razor UI Controls

The Razor UI Controls are utilized in new Razor project templates and the World Validation Application.

UI Component List

Currently the component libraries include common Bootstrap UI Form Controls and Navigation Components:

Control Description
@Html.ValidationSummary Show validation summary error message unless there's an error in specified fields
@Html.ValidationSuccess Display a "Success Alert Box"
@Html.FormInput Display a <input type="text"/> UI Control
@Html.FormTextarea Display a <textarea/> UI Control
@Html.FormSelect Display a <select/> UI Control
@Html.FormInput Display a <input type="checkbox"/> UI Control
@Html.HiddenInputs Emit HTML <input type="hidden"/> field for each specified Key/Value pair entry
@Html.SvgImage Return <svg/> markup for the named image
@Html.Nav Display a list of NavItem's
@Html.Navbar Display the navbar main menu
@Html.NavLink Display a nav-link nav-item
@Html.NavButtonGroup Display a list of NavItem's btn-group

Bootstrap UI Form Controls

The Bootstrap UI form controls include built-in support for validation where they can render validation errors from ServiceStack's ResponseStatus object, e.g the Login Page in World Validation:

<form action="/auth/credentials" method="post" class="col-lg-4">
    <div class="form-group">
        @Html.ValidationSummary(new[]{ "userName","password" }, 
            new { @class = "alert alert-warning" })
        
        @Html.HiddenInputs(new { 
            @continue = Html.Query("continue") ?? "/server-razor/",
            errorView = "/server-razor/login"
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { id = "userName" }, new InputOptions {
            Label = "Email",
            Help  = "Email you signed up with",
            Size  = "lg",
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { id = "password", type = "password" }, new InputOptions {
            Label = "Password",
            Help  = "6 characters or more",
            Size  = "lg",
            PreserveValue = false,
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { 
            id   = "rememberMe", 
            type = "checkbox",
            @checked = true,
        },
        new InputOptions { Label = "Remember Me" })
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-lg btn-primary">Login</button>
    </div>
    <div class="form-group">
        <a class="lnk" href="/server-razor/register">Register New User</a>
    </div>
</form>

Login Page UI

The Login Page contains a standard Bootstrap Username/Password form with labels, placeholders and help text, which initially looks like:

What it looks like after submitting an empty form with Server Exception Errors rendered against their respective fields:

Form Control Properties

The Razor controls uses anonymous objects and camelCase properties for its unbounded HTML Element Attribute List for attributes you want to add to the underlying HTML <input/> Element and a Typed InputOptions Class to specify the controls other high-level features, typically like:

@Html.ControlName(new { /*htmlAttrs*/ }, new InputOptions { ... })

The typed InputOptions class supports the following features:

/// High-level Input options for rendering HTML Input controls
public class InputOptions
{
    /// Display the Control inline 
    public bool Inline { get; set; }
    
    /// Label for the control
    public string Label { get; set; }
    
    /// Class for Label
    public string LabelClass { get; set; }
    
    /// Override the class on the error message (default: invalid-feedback)
    public string ErrorClass { get; set; }

    /// Small Help Text displayed with the control
    public string Help { get; set; }
    
    /// Bootstrap Size of the Control: sm, lg
    public string Size { get; set; }
    
    /// Multiple Value Data Source for Checkboxes, Radio boxes and Select Controls 
    public object Values { get; set; }

    /// Typed setter of Multi Input Values
    public IEnumerable<KeyValuePair<string, string>> InputValues
    {
        set => Values = value;
    }

    /// Whether to preserve value state after post back
    public bool PreserveValue { get; set; } = true;

    /// Whether to show Error Message associated with this control
    public bool ShowErrors { get; set; } = true;
}

Contacts Page

The Contacts Page shows a more complete example with a number of different UI Controls.

<form action="/contacts" method="post" class="col-lg-4">
    <div class="form-group">
        @Html.ValidationSummary(new[]{ "title","name","color","age","filmGenres","agree" })
        @Html.HiddenInputs(new { @continue = Continue, errorView = Continue })
    </div>
    <div class="form-group">
        @Html.FormInput(new { 
            id = "title", 
            type = "radio",
        }, new InputOptions { 
            Values = Html.ContactTitles(),
            Inline = true,
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { 
            id = "name", 
            placeholder = "Name", 
        }, new InputOptions {
            Label = "Full Name",
            Help  = "Your first and last name",
        })
    </div>
    <div class="form-group">
        @Html.FormSelect(new { 
            id = "color", 
            @class = "col-4", 
        }, new InputOptions {
            Label  = "Favorite color",
            Values = new StringDictionary { {"",""} }.Merge(Html.ContactColors()),
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { 
            id = "filmGenres", 
            type = "checkbox",
        }, new InputOptions { 
            Label  = "Favorite Film Genres",
            Help   = "choose one or more",
            Values = Html.ContactGenres()
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new { 
            id = "age", 
            type = "number",
            min = 13,
            placeholder = "Age",
            @class = "col-3",
        })
    </div>
    <div class="form-group">
        @Html.FormInput(new {
            id   = "agree",
            type = "checkbox",
        },
        new InputOptions { Label = "Agree to terms and conditions" })
    </div>
    <div class="form-group">
        <button class="btn btn-primary" type="submit">Add Contact</button>
        <a href="/server-razor/contacts/">reset</a>
    </div>
</form>

Both Server UI Controls provide auto Validation Form Binding for any validation rules specified on the CreateContact Validator:

public class CreateContactValidator : AbstractValidator<CreateContact>
{
    public CreateContactValidator()
    {
        RuleFor(r => r.Title).NotEqual(Title.Unspecified).WithMessage("Please choose a title");
        RuleFor(r => r.Name).NotEmpty();
        RuleFor(r => r.Color).Must(x => x.IsValidColor()).WithMessage("Must be a valid color");
        RuleFor(r => r.FilmGenres).NotEmpty().WithMessage("Please select at least 1 genre");
        RuleFor(r => r.Age).GreaterThan(13).WithMessage("Contacts must be older than 13");
        RuleFor(x => x.Agree).Equal(true).WithMessage("You must agree before submitting");
    }
}

As well as any ArgumentException thrown within the Service Implementation:

public object Any(CreateContact request) 
{
    var newContact = request.ConvertTo<Data.Contact>();
    newContact.Id = Interlocked.Increment(ref Counter);
    newContact.UserAuthId = this.GetUserId();
    newContact.CreatedDate = newContact.ModifiedDate = DateTime.UtcNow;

    var contacts = Contacts.Values.ToList();
    var alreadyExists = contacts.Any(x => x.UserAuthId == newContact.UserAuthId && x.Name == request.Name);
    if (alreadyExists)
        throw new ArgumentException($"You already have contact named '{request.Name}'",nameof(request.Name));
    
    Contacts[newContact.Id] = newContact;
    return new CreateContactResponse { Result = newContact.ConvertTo<Contact>() };
}

Contacts Page UI

The Contacts Page is representative of a more complex page that utilizes a variety of different form controls where the same page is also responsible for rendering the list of existing contacts:

Here's an example of what a partially submitted invalid form looks like:

To view the complete implementation in context checkout World Validation Server Implementation.

The Server Navigation Controls are used to render your Apps Unified Navigation where you can use the @Html.Navbar() and @Html.NavButtonGroup() methods to render NavItems:

You can render the main menu navigation using the Navbar HTML Helper:

@Html.Navbar()

Which by default renders the View.NavItems main navigation, using the default NavOptions and User Attributes (if authenticated):

You can also render a different Navigation List with:

@Html.Navbar(Html.GetNavItems("submenu"))

Which can be customized using the different NavOptions properties above, in camelCase:

@Html.Navbar(Html.GetNavItems("submenu"), new NavOptions {
    NavClass = "navbar-nav navbar-light bg-light" 
})

Button group

The NavButtonGroup HTML Helper can render NavItems in a button group, e.g. the OAuth buttons are rendered with:

@Html.NavButtonGroup(Html.GetNavItems("auth"), new NavOptions {
    NavClass = "",
    NavItemClass = "btn btn-block btn-lg",
})

Which renders a vertical, spaced list of buttons which look like:

Razor Pages

The same server controls are available in ServiceStack.Razor Apps as HTML Helper extension methods:

@Html.Navbar()

@Html.Navbar(Html.GetNavItems("submenu"))

@Html.Navbar(Html.GetNavItems("submenu"), new NavOptions {
    NavClass = "navbar-nav navbar-light bg-light"
})
@Html.NavButtonGroup(Html.GetNavItems("auth"), new NavOptions {
    NavClass = "",
    NavItemClass = "btn btn-block btn-lg",
})

Each Nav UI Control can be further customized by overriding the properties on the typed NavOptions class:

public class NavOptions
{
    /// User Attributes for conditional rendering, e.g:
    ///  - auth - User is Authenticated
    ///  - role:name - User Role
    ///  - perm:name - User Permission 
    public HashSet<string> Attributes { get; set; }
    
    /// Path Info that should set as active 
    public string ActivePath { get; set; }
    
    /// Prefix to include before NavItem.Path (if any)
    public string BaseHref { get; set; }

    // Custom classes applied to different navigation elements (defaults to Bootstrap classes)
    public string NavClass { get; set; }
    public string NavItemClass { get; set; }
    public string NavLinkClass { get; set; }
    
    public string ChildNavItemClass { get; set; }
    public string ChildNavLinkClass { get; set; }
    public string ChildNavMenuClass { get; set; }
    public string ChildNavMenuItemClass { get; set; }
}