Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 3767

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 61k
Author
Asked: November 26, 20242024-11-26T05:45:09+00:00 2024-11-26T05:45:09+00:00

Reusable MVC Razor Components with HTML Extension Methods

  • 61k

Component reuse is one of the intrinsic features of modern JavaScript frameworks. React, Angular and their counterparts all guide us toward the idea of creating small, reusable elements in order to maintain a clean codebase.

MVC performs a similar task, but the architecture doesn't make it as intuitive. As the name suggests, it provides reusable components as standard; unfortunately these components can be relatively unsophisticated making easy to fall into the trap of creating massive views and forgetting about reusability.

On occasion, the time required to migrate existing solutions to new frameworks is prohibitive, but that doesn't mean that we can't provide an easy to use component library within the confines of MVC Razor.

By simplifying the our source code we can take back control of styling, reduce time to onboard new team members and standardise component usage to prevent subtle differences seeping through in different product areas.

Approaches will vary depending on the complexity of your components, but we can take inspiration from providers of third party libraries and make our own HTML helpers accessible directly through Visual Studio IntelliSense.

What are HTML Helpers?

For anyone not familiar with the concept, we can use extension methods to expand on the functionality of any data type. This Microsoft document explains it in more detail, but essentially we declare something like this:

namespace MyExtensionMethods {     public static class MyExtensions     {         public static int WordCount(this string str)         {             return str.Split(new char[] { ' ', '.', '?' },                 StringSplitOptions.RemoveEmptyEntries).Length;         }     } } 
Enter fullscreen mode Exit fullscreen mode

Whenever the namespace is available we can execute the WordCount function against any string type:

var stringValue = "There are four words"; var wordCount = stringValue.WordCount(); // outputs 4. 
Enter fullscreen mode Exit fullscreen mode

This same concept can be applied to the HTML helpers made available by Razor, we can leverage the same approach to make it easier to create & recreate common components; improving discoverability for new and existing developers alike.

@(Html.MyHtmlHelper()     .GenerateThisControl("controlId")     .EnableOptions(true) ); 
Enter fullscreen mode Exit fullscreen mode

Basic Usage

A basic example might be something like this, which expands on the functionality of Html.Raw() to escape quotation marks for inclusion in DOM attributes:

using System.Web; using System.Web.Mvc;  namespace MyExtensionMethods {     public static class HtmlStringHelper     {         public static IHtmlString RawHtmlString(             this HtmlHelper htmlHelper, string value)         {             return !string.IsNullOrWhiteSpace(value)                  ? htmlHelper.Raw(EscapeQuotes(value))                 : "";         }     } } 
Enter fullscreen mode Exit fullscreen mode

An example like this is of course quite limited. We can use it to perform basic operations in day-to-day work, but it's not something which will reduce code complexity by any measurable amount.

But we can expand on this to return entire views:

namespace MyExtensionMethods {     public static class HtmlStringHelper     {         public IHtmlString LoadingAnimation(             this HtmlHelper htmlHelper)         {             return _htmlHelper.Partial(                 "~/Views/HtmlHelpers/LoadingAnimation.cshtml");         }     } } 
Enter fullscreen mode Exit fullscreen mode

There's still not much benefit to this last example, certainly nothing that we can't achieve by documenting the location of the loading animation view, but this shows us that we can:

  • Use HTML Helpers to store content outside of the core solution. We could make this available as part of a DLL and perhaps directly render the HTML output from an embedded file. This would let us simplify create a component library which can be accessed by multiple projects.

  • Guide developers through the generation of HTML content by providing additional configuration options without the need to go digging for the right model or view name.

Helper Factory

To provide more advanced functionality, we'll have to expose additional options via helper factories.

This approach follows previous examples, except this time we're returning a class which implements IHtmlString instead of a partial view or string.

First, we add something like this to our existing helper:

public static HtmlHelperFactory MyProductName(this HtmlHelper helper) {     return new HtmlHelperFactory(helper); } 
Enter fullscreen mode Exit fullscreen mode

Which is implements a HtmlHelperFactory class:

namespace MyExtensionMethods.HtmlHelpers.Factory {     public class HtmlHelperFactory     {         private readonly HtmlHelper _htmlHelper;          public HtmlHelperFactory(HtmlHelper htmlHelper)         {             _htmlHelper = htmlHelper;         }          public TooltipBuilder Tooltip(string id, string text)         {             return new TooltipBuilder(_htmlHelper, id, text);         }     } } 
Enter fullscreen mode Exit fullscreen mode

What we have here will allow us to generate a new tooltip component within our CSHTML files by entering something like @Html.MyProductName().Tooltip("id", "This is my tooltip").

We can imagine that this would perhaps generate a little “i” icon, with the specified tooltip text automatically escaped and assigned within the title attribute, perhaps using a third party component to make the tooltip look pretty.

Instead of creating the HTML content within the helper, we can shift it to a builder class, provide it with some additional options and create an appropriate view model.

public class TooltipBuilder : IHtmlString {     private readonly HtmlHelper _htmlHelper;     private readonly TooltipModel _model = new TooltipModel();      public TooltipBuilder(         HtmlHelper htmlHelper,          string id = "",          string tooltipText = "")     {         _model.Id = id;         _model.Text = tooltipText;         _htmlHelper = htmlHelper;     }      public TooltipBuilder Colour(string value)     {         _model.Colour = value;         return this;     }      public TooltipBuilder Mode(TooltipBuilderMode value)     {         _model.Mode = value;         return this;     }      public string ToHtmlString()     {         var returnData = _htmlHelper             .Partial(                 "~/Views/HtmlHelpers/Tooltip.cshtml",                  _model             )             .ToHtmlString();          return returnData;     } } 
Enter fullscreen mode Exit fullscreen mode

We can expect the generated tooltip to be pretty simplistic, perhaps a couple of <span>'s and a custom font used to generate an appropriate icon.

But what we've done here is to standardise this simple element and ensure that every instance is rendered in the same way. Should we ever want to change the icon then we'll only ever need to update a single line of code.

We've also given other developers a list of possible options in a manner which lets us chain operations together.

@(Html.MyProductName()     // provide required fields     .Tooltip("id", "This is my tooltip")     // provide optional parameters     .Colour("#575757")     .Mode(TooltipBuilderMode.FloatBottom) ) 
Enter fullscreen mode Exit fullscreen mode

Html Attributes

While the tooltip example is pretty basic, we might not want every instance of the same object to behave in exactly the same way, perhaps we want to allow users to specify some additional classes on the parent container, or maybe we want to bind the tooltip value to a model.

In which case we might expand on TooltipBuilder by introducing some extra operations and enhanced processing:

public TooltipBuilder(     HtmlHelper htmlHelper,      string id = "",      string defaultTooltipText = "") {     _model.Id = id;     _model.Text = defaultTooltipText;     _htmlHelper = htmlHelper; }  // provide generic attribute options public TooltipBuilder HtmlAttributes(     IDictionary<string,object> value) {     if (value != null)     {         value.ToList().ForEach(v => SetHtmlAttribute(v));     }     return this; }  // provide more specific guided attribute options public TooltipBuilder TitleBindingAttribute(string value) {     SetHtmlAttribute(new KeyValuePair<string,object>(         "data-bind",          $"attr: {{ title: {value} }}"     ));      return this; }  // combine lists into a single property for processing private void SetHtmlAttribute(     KeyValuePair<string, object> attribute) {     if (!string.IsNullOrWhiteSpace(attribute.Value?.ToString() ?? ""))     {         if (_model.HtmlAttributes.ContainsKey(attribute.Key))         {             _model.HtmlAttributes[attribute.Key] = attribute.Value;         }         else         {             _model.HtmlAttributes.Add(attribute.Key, attribute.Value);         }     } }  public string ToHtmlString() {     var returnData = _htmlHelper         .Partial(             "~/Views/HtmlHelpers/Tooltip.cshtml",              _model         )         .ToHtmlString();     return returnData; } 
Enter fullscreen mode Exit fullscreen mode

With this, we can allow more in-depth customisation while still retaining control over the appearance and behaviour of the tooltip:

@(Html.MyProductName()     .Tooltip("id", "This is my tooltip")     .SetHtmlAttribute(         [{@class = "myClassName"}]      )     .TitleBindingAttribute("tooltipTextField") ) 
Enter fullscreen mode Exit fullscreen mode

Grouping Commands

Finally, we may find ourselves in a situation where we have so many options within our helper that we want to group them by function to make them easier to work with. (Perhaps hard to believe within the confines of the tooltip example)

We can do this by creating a configurator class and using an Action delegate to preserve the simplicity of creating our control within a single razor command.

First we need a basic option factory:

public class TooltipOptionFactory {     internal TooltipOptions Options =          new TooltipOptions();      public TooltipOptionFactory Disabled(bool value)     {         Options.Disabled = value;         return this;     }      public TooltipOptionFactory Visible(bool value)     {         Options.Visible = value;         return this;     } } 
Enter fullscreen mode Exit fullscreen mode

Then we need to integrate it with our TooltipBuilder class:

public TooltipBuilder Options(     Action<TooltipOptionFactory> configurator) {     var optionFactory = new TooltipOptionFactory();     configurator.Invoke(optionFactory);     _model.Options = optionFactory.Options;     return this; } 
Enter fullscreen mode Exit fullscreen mode

Which will allow us to use it within our CSHTML like this:

@(Html.MyProductName()     .Tooltip("id", "This is my tooltip")     .Options(o => {         o.Visible(true);         o.Disabled(false);     }) ) 
Enter fullscreen mode Exit fullscreen mode

These are just the first stepping stones in creating a library of reusable Razor components. While there's clearly some overhead in creating a full library of these components the future time savings from taking this approach are immeasurable.

As with many approaches, it's much easier to begin a project with something like this in mind rather than shoehorning it into an established product; as someone who recently spent two weeks fixing issues caused by the upgrade of a third party component library – I really wish our development team had taken this approach 10 years ago, but now we have the tools to correct that mistake.

mvcrazortutorialwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    How to ensure that all the routes on my Symfony ...

    • 0 Answers
  • Author

    Insights into Forms in Flask

    • 0 Answers
  • Author

    Kick Start Your Next Project With Holo Theme

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.