I've got the following snippet of Razor code, that exists in probably 15 different pages, that I'd like to reuse, if possible:
<div class="col-xs-12">
#if (#Model.Rating == 0)
{
<img src="/Images/Rating/NoRating.jpg" alt="" width="125">
}
else if (#Model.Rating == 1)
{
<img src="/Images/Rating/One.jpg" alt="" width="125">
}
else if (#Model.Rating == 2)
{
<img src="/Images/Rating/Two.jpg" alt="" width="125">
}
else if (#Model.Rating == 3)
{
<img src="/Images/Rating/Three.jpg" alt="" width="125">
}
else if (#Model.Rating == 4)
{
<img src="/Images/Rating/Four.jpg" alt="" width="125">
}
else if (#Model.Rating == 5)
{
<img src="/Images/Rating/Five.jpg" alt="" width="125">
}
</div>
What I would love to be able to do is to call a method and have the method return this code where I have it in my Razor .cshtml file. The method would also have to accept a parameter. In this case, the parameter would be a rating value of between 0 and 5. I would then replace all occurrences of #Model.Rating with the parameter value. Is it possible to do this? I'd rather not have to resort to a partial view if possible.
What I would love to be able to do is to call a method and have the method return this code where I have it in my Razor .cshtml file. The method would also have to accept a parameter. In this case, the parameter would be a rating value of between 0 and 5. I would then replace all occurrences of #Model.Rating with the parameter value. Is it possible to do this?
Solution 1:
You can create an extension method of HtmlHelper class like this :
public static class RatingExtensions
{
public static MvcHtmlString Rating(this HtmlHelper helper, short rating)
{
var imageSrc = "/Images/Rating/";
switch (rating)
{
case 0:
imageSrc += "NoRating.jpg";
break;
case 1:
imageSrc += "One.jpg";
break;
// And so on....
default:
throw new IndexOutOfRangeException(string.Format("The following rating: {0} is not expected.",
rating));
}
return new MvcHtmlString(String.Format("<img src='{0}' alt='' width='125' />", imageSrc));
}
}
In your view after importing the namespace of your extension method into the view, you call your extension method by writing this line:
#Html.Rating(Model.Rating)
Solution 2:
Just create a partial view and put it into the Shared sub-folder of your Views folder. Lets name it _Ratring.cshtml. The content of this file muste be the following (Notice #model directive which is in short type):
#model short
#{
var imageSrc = "/Images/Rating/";
switch (Model)
{
case 0:
imageSrc += "NoRating.jpg";
break;
case 1:
imageSrc += "One.jpg";
break;
// And so on....
default:
throw new IndexOutOfRangeException(string.Format("The following rating: {0} is not expected.",
Model));
}
}
<div class="col-xs-12">
<img src="#imageSrc" alt="" width="125">
</div>
You use this solution in your view by call Html.RenderPartial method liek this :
#{
Html.RenderPartial("_Rating", Model.Rating);
}
Solution 1 is better because you can move the extension method in its own assembly project and use it accross multiple projects.
Option 1
You may create a custom Html helper method. I would prefer to rename the image name from One.jpg to 1.jpg so that you do not need to write much code from the number passed into the string representation of that. You can simply relate your Model.Rating value to the image name as they directly match.
But if you still want to keep the image names as the string way, You may need to write a switch statement inside your method to convert the integer value to the string value (1 to One, 2 to Two etc..). The problem with this approach is, If you ever add a new rating like 12 or 13, you need to go to this file and update your switch statements again ! So i prefer the first approach of renaming the image names to match with the numeric representation of Model.Rating value
public static class MyCustomImageExtensions
{
public static MvcHtmlString RatingImage(this HtmlHelper helper, int imageId,
string alt,int width)
{
var path = "Images/Rating/NoRating.jpg";
if (imageId > 0)
{
path = string.Format("Images/Rating/{0}.jpg", imageId);
}
return new MvcHtmlString(string.Format("<img src='{0}' alt='{1}' width='{2}'>"
, path, alt, width));
}
}
You may call it in your razor view like
#Html.RatingImage(Model.Rating,"alt test",250)
You may add the Alternate text property and width to your model so that you do not need to hard code it in your main view.
Option 2
Since you are not doing much logic inside the helper method, you may simply use partial view where you will have the markup you want to use and pass the model properties to that.
I would change a lot, but it's really worth it. Create your own ModelMetadataProvider.
public class MyModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName)
{
var result = base.GetMetadataForProperty(modelAccessor, containerType, propertyName);
if (string.IsNullOrEmpty(result.TemplateHint)
&& typeof(Enum).IsAssignableFrom(result.ModelType))
{
result.TemplateHint = result.ModelType.ToString();
}
return result;
}
}
Register it in the global.asax
ModelMetadataProviders.Current = new MyModelMetaDataProvider();
Create an Enum:
public enum StarRating
{
NoRating = 0,
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5
}
Update your model
public class SomeMethodViewModel
{
public StarRating Rating { get; set; }
}
Create a Display template
/Views/Shared/DisplayTemplates/StarRating.cshtml
#model StarRating
<img src="/Images/Rating/#(Model.ToString()).jpg" alt="" width="125">
In your view:
#Html.DisplayFor(m => m.Rating)
Related
I want to create complicate form which will create structure of wizard, partial steps form, validation and submit. This structure have to use model attributes annotations to create one structure object over the model. So after reflection I have model and one other class with structure description. All properties within are strings with Fields which I have to pass on 'asp-for' tag helper. So part of the code is:
#foreach(var field in #group.Fields) {
<div class="col-12 col-md-6 col-lg-4">
<div class="form-group md-form md-outline">
<label asp-for="#field.Name" class="control-label"></label>
<input asp-for="#field.Name" class="form-control" />
<span asp-validation-for="#field.Name" class="text-danger"></span>
</div>
</div>
}
This is nor working because tag helper expect expression and generate wrong values which are not expected from me. The value in #field.Name is 'PostAddress.Street1'. If I replace all of "#field.Name" with "PostAddress.Street1" everything work properly how I expected.
It looks small issue but I'm trying many things and reading some theads in forums but didn't find the answer. What I tried:
Experiment 1
Tried to inherit InputTagHelper class from dotnet library and override property For but without success. It changed ModelExpression but no changes in interface. May be base class have some logic to skip this changed object or is not correct generated:
[HtmlAttributeName("asp-for")]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (value.Model != null)
{
var viewData = this.ViewContext.ViewData as ViewDataDictionary<AbnServiceModel>;
me = ModelExpressionProvider.CreateModelExpression<AbnServiceModel, string>(viewData, model => model.PostAddress.Street1);
}
base.For = me;
}
}
=================================================
2. Experiment 2
Try to get original implementation from .NET Core code and made some modification in code to fix the issue. But the code and dependencies with internal libraries were very complicated and I reject this idea.
Expiriment 3
Using HTML helpers
#Html.Label(#field.Name, "", new{ #class="control-label" })
#Html.Editor(#field.Name, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessage(#field.Name,"",new { htmlAttributes = new{ #class="text-danger" } })
It render components correct into the browser but client side validation using jquery.validate.unobtrusive.js is not working. Not sure why.
Expiriment 4
Using HTML helpers:
#Html.LabelFor(m=>m.PostAddress.Street1, new{ #class="control-label" })
#Html.EditorFor(m=>m.PostAddress.Street1, new { htmlAttributes = new{ #class="form-control" } })
#Html.ValidationMessageFor(m=>m.PostAddress.Street1,"",new { htmlAttributes = new{ #class="text-danger" } })
The validation is working but class weren't applied well, may be my mistake. But other problem here is that I'm not using expression which is string which can get from model object. Also It doesn't catch all logic which is included in asp-for tag helper.
Experiment 5
Tried to create my own tag helper and using generator to create the content html. But this means that I have to implement all logic like helper in dotnet core to have all functionality which is same like Expiriment 2
So I didn't find good solution of this "simple" problem and lost some days to investigate and doing some code to resolve it. I'm surprised that no way to pass string variable with property name and it wouldn't work.
Can someone help me to fix this problem with real example? I didn't find the answer in all posts. I want to have all logic from asp-for tag helper but use variable to pass the expression. It cab be and tricky, just want to have some resolution to continue with my project.
Thank you
I resolved my issue.
Created one helper method:
public static class CommonHelperMethods
{
public static ModelExplorer GetModelExplorer(this ModelExplorer container, string field, IModelMetadataProvider modelMetadataProvider = null)
{
ModelExplorer result = container;
var fields = field.Split(".").ToList();
var match = Regex.Match(fields[0], #"(.+)\[(\d)+\]");
if (!match.Success)
{
fields.ForEach(x =>
{
result = result?.GetExplorerForProperty(x) ?? result;
});
}
else
{ //List have to create own Property browser
string proName = match.Groups[1].Value;
int idx = Convert.ToInt32(match.Groups[2].Value);
var model = ((IList)result?.GetExplorerForProperty(proName).Model)[idx];
var targetProperty = model.GetType().GetProperty(fields[1]);
var targetValueModel = targetProperty.GetValue(model);
var elementMetadata = modelMetadataProvider.GetMetadataForProperty(model.GetType(), fields[1]);
return new ModelExplorer(modelMetadataProvider, container, elementMetadata, targetValueModel);
}
return result;
}
}
And just override the tag helper class with this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace GetTaxSolutions.Web.Infrastructure.TagHelpers
{
[HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class InputTextGtTaxHelper : InputTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("not-exp")]
public bool NotExpression { get; set; } = false;
[HtmlAttributeName(ForAttributeName)]
public new ModelExpression For
{
get
{
return base.For;
}
set
{
ModelExpression me = value;
if (NotExpression)
{
var modelExplorertmp = value.ModelExplorer.Container.GetModelExplorer(value.Model.ToString(), ModelMetadataProvider);
var modelExplorer = new ModelExplorer(ModelMetadataProvider, value.ModelExplorer.Container, modelExplorertmp.Metadata, modelExplorertmp.Model);
me = new ModelExpression(value.Model.ToString(), modelExplorer);
}
base.For = me;
}
}
public IModelExpressionProvider ModelExpressionProvider { get; }
public IModelMetadataProvider ModelMetadataProvider { get; }
public IActionContextAccessor Accessor { get; }
public InputTextGtTaxHelper(
IHtmlGenerator generator,
IModelExpressionProvider modelExpressionProvider,
IModelMetadataProvider modelMetaDataProvider) : base(generator)
{
ModelExpressionProvider = modelExpressionProvider;
ModelMetadataProvider = modelMetaDataProvider;
}
}
}
Also should skip original class in tag helper registration:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.LabelTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper GetTaxSolutions.Web.Infrastructure.TagHelpers.*, GetTaxSolutions.Web
And when use model in expression just have to pass attribute 'no-exp' on input elements. Otherwise will work like original tag helper.
<input not-exp="true" asp-for="#field.Name" class="form-control" />
Also you have to do same with label, select and other used tag helpers which you want to support this way of model passing.
How can I get both the image and tooltip in a single call to a MVC controller method? I can use something like the following to get the image, but how to also get the associated tooltip? The use case is to display an image if the user is allowed to see the image, else display a generic image and a tooltip indicating why the image is not being shown.
To clarify, I would like to avoid two calls to the controller, once to get the image path and tooltip, and another to get the image file. Not only will this result in two round trips across the network, it would also repeat the validation checks. The problem is that the img src call only accommodates the image, not other properties such as the title associated with the image.
<img src="#Url.Action("GetPicture", "User", new { userId = Model.User.Id })" />
Can't you just have a second method for GetTitle using the same permission logic from GetImage and return the appropriate text for each user? Then call this method for the title attribute.
Can you approach the problem in the following way.
Create Model
public class ImageViewModel
{
public string ImagePath
{
get;
set;
}
public string ImageTitle
{
get;
set;
}
}
Create a partial View
#using StackOverFlowProject.Models
#model ImageViewModel
<img src=#Model.ImagePath title=#Model.ImageTitle />
Your Controller
public PartialViewResult _Image(string userID)
{
ImageViewModel model = new ImageViewModel();
//Here You can check what image and tooltip you want show to the user
//model = //FillData from DB /
return PartialView("_Image", model);
}
and at last in Your MainView where you want to display the Image render the partial view
Not sure if this is the best way to do it, but I did it by creating an Ajax form, submitting it using jQuery, returning a JSON object with the byte array encoded as a Base64 string, and using Javascript to display the image. Seems to be working so far, will know more from further tests.
In the view:
<div id="imgDiv">
#using (Ajax.BeginForm("GetImg", "User", null, new AjaxOptions()
{
HttpMethod = "POST",
Url = Url.Action("GetImg", "User"),
OnSuccess = "DisplayImageWithTooltip(data, 'imgDiv')",
}, new { id = "ImgForm", #class = "imageGetterWithTooltip" }))
{
#Html.AntiForgeryToken()
#Html.Hidden("userId", #Model.User.Id)
}
</div>
Javascript to submit form:
$(".imageGetterWithTooltip").submit();
In Controller (based on https://stackoverflow.com/a/9464137/1385857)
return Json(new
{
fileBytes = Convert.ToBase64String(<File byte[]>),
fileType = <FileType>,
tooltip = <toolTip>
}, JsonRequestBehavior.AllowGet);
Javascript to display image
function DisplayImageWithTooltip(data, target) {
var oImg = document.createElement("img");
oImg.width = 150;
oImg.height = 150;
oImg.setAttribute('src', "data:" + data.fileType + ";base64," + data.fileBytes);
oImg.setAttribute('title', data.tooltip);
document.getElementById(target).appendChild(oImg);
}
Using Manish's ideas, my simplified solution is to create a partial view and supply it the image data directly:
Controller:
vmMiniData data = new Models.vmMiniData();
byte[] byteArray = Users.GetPersonnelImage(personnelID);
if (byteArray != null)
{
data.ImageStr = Convert.ToBase64String(byteArray);
}
else
{
data.ImageStr = Convert.ToBase64String(Users.GetPersonnelImage("00000000-0000-0000-0000-000000000000")); //get blank image
}
data.CaptionStr = Users.GetUserJobTitle(personnelID);
return PartialView("Personnel/MiniPersonnelPartial", data);
Model:
public static byte[] GetPersonnelImage(string personnelID)
{
byte[] img = (byte[])(from record in db.PersonnelImages
.Where(R => R.PersonnelID == new Guid(personnelID))
select record.Image).FirstOrDefault();
return img;
}
Then in the partial:
#model vmMiniData
<div>
<div>#Model.CaptionStr</div>
<div> <img src="data:image;base64,#Model.ImageStr" style="width:60px;min-height:30px;" /></div>
</div>
It works very well in MVC 5 :).
So I have a controller like this:
public class TestController : Controller
{
//
// GET: /Test/
public ActionResult Index()
{
return View("Test");
}
public ActionResult Post(IList<Test> LanguageStrings, IList<Test> LanguageStringsGroup, IList<string> Deleted, IList<string> DeletedGroup)
{
if (LanguageStrings == null)
{
throw new ApplicationException("NULL");
}
return View("Test");
}
}
public class Test
{
public string Val { get; set; }
public string Another { get; set; }
}
And a view like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
When I post the form my controller throws the exception because LanguageStrings is null. The strange part I mentioned in the title is that if I add one more record to the list everything works.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStrings[2].Val", "test3")
#Html.Hidden("LanguageStrings[2].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("Deleted[0]")
#Html.Hidden("Deleted[1]")
#Html.Hidden("Deleted[2]")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
It also works when I remove the "Deleted" list.
Like this:
<h2>Test</h2>
#using (Html.BeginForm("Post", "Test"))
{
#Html.Hidden("LanguageStrings[0].Val", "test1")
#Html.Hidden("LanguageStrings[0].Another")
#Html.Hidden("LanguageStrings[1].Val", "test2")
#Html.Hidden("LanguageStrings[1].Another")
#Html.Hidden("LanguageStringsGroup[0].Val", "test4")
#Html.Hidden("DeletedGroup[0]")
<button>Post</button>
}
This has something to do with the naming I am using. I have already solved the problem with renaming LanguageStrings to something else. But I would like to understand what is happening here because probably I could learn something from it how MVC maps request body and will be able to avoid similar time consuming problems.
Please help me and explain the cause of this.
You found a bug in the PrefixContainer of MVC 4 which has already been fixed in MVC 5.
Here is the fixed version with comments about the bug:
internal bool ContainsPrefix(string prefix)
{
if (prefix == null)
{
throw new ArgumentNullException("prefix");
}
if (prefix.Length == 0)
{
return _sortedValues.Length > 0; // only match empty string when we have some value
}
PrefixComparer prefixComparer = new PrefixComparer(prefix);
bool containsPrefix = Array.BinarySearch(_sortedValues, prefix, prefixComparer) > -1;
if (!containsPrefix)
{
// If there's something in the search boundary that starts with the same name
// as the collection prefix that we're trying to find, the binary search would actually fail.
// For example, let's say we have foo.a, foo.bE and foo.b[0]. Calling Array.BinarySearch
// will fail to find foo.b because it will land on foo.bE, then look at foo.a and finally
// failing to find the prefix which is actually present in the container (foo.b[0]).
// Here we're doing another pass looking specifically for collection prefix.
containsPrefix = Array.BinarySearch(_sortedValues, prefix + "[", prefixComparer) > -1;
}
return containsPrefix;
}
I have had much more success with #Html.HiddenFor() for posting back to the controller. Code would look something like this:
#for (int i = 0; i < #Model.LanguageStrings.Count; i++)
{
#Html.HiddenFor(model => model.LanguageStrings[i].Val, string.Format("test{0}", i))
#Html.HiddenFor(model => model.LanguageStrings[i].Another)
}
Most HTML helper methods have a "For" helper that is intended to be used for binding data to models. Here is another post on the site that explains the "For" methods well: What is the difference between Html.Hidden and Html.HiddenFor
I have a controller method as below to send an image to a MVC view to display
public FileResult ShowImage(GuidID)
{
DataServiceClient client = new DataServiceClient ();
AdviserImage result;
result = client.GetAdviserImage(ID);
return File(result.Image, "image/jpg" );
}
in my view I am using
<img src="<%= Url.Action("ShowImage", "Adviser", new { ID = Model.AdviserID }) %>" alt="<%:Model.LicenceNumber %>" />
to display the image
but some ids does not have a image and returning null, I want to check the file result is null withing the view and if its null not not to display the image.
You will need another, separate controller action that checks the datastore and returns ContentResult which will be either true or false (or some other string you want to tell whether an ID has the bytes or not) and then in the view you will need this:
if(#Html.Action("action", "controller").ToString().Equals("true", StringComparison.OrdinalIgnoreCase)){
// render image tag with the call to the other action that returns FileResult
}
The other option is that you have a view model which contains a reference to the image bytes. That way you prepare the model for the view (the parent model) in the controller and pull the bytes for the image there, then in the view you would have:
if(Model.ImageBytes.Length() > 0) {
... do something
}
with ImageBytes property being of type byte[]
For instance, this is a snippet from one of my views:
#model pending.Models.Section
#if (Model != null && Model.Image != null && Model.Image.ImageBytes.Count() > 0)
{
<a href="#Model.Url" rel="#Model.Rel">
<img title="#Model.Title" alt="#Model.Title" src="#Url.Action(MVC.Section.Actions.Image(Model.Id))" /></a>
}
HTH
Why not check for null in your controller and leave the logic out of the display:
result = client.GetAdviserImage(ID);
if (result == null)
{
result = AdviserImage.Missing;
}
You can create a default image and make it static. If you really don't want to display the image then create an Html extension method to keep the logic out of the view:
public static string AdviserImage(this HtmlHelper helper, AdviserImage image, int id, int lic)
{
if (image != null)
{
string url = string.Format("/Adviser/ShowImage/{0}", id);
string html = string.Format("<img src=\"{0}\" alt=\"{1}\" />", url, image.lic);
return html;
}
return string.Empty; // or other suitable html element
}
I have the following RazorEngine call:
public class RazorEngineRender
{
public static string RenderPartialViewToString(string templatePath, string viewName, object model)
{
string text = System.IO.File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Razor.Parse(text, model);
return renderedText;
}
}
This is called from:
_emailService.Render(TemplatePath, "Email.cshtml", new { ActivationLink = activationLink });
I also have this view file (email.cshtml):
<div>
<div>
Link: #Model.ActivationLink
</div>
</div>
When the call to Razor.Parse() occurs, I always get a:
Unable to compile template. Check the Errors list for details.
The error list is:
error CS1061: 'object' does not contain a definition for 'ActivationLink' and no extension method 'ActivationLink' accepting a first argument of type 'object' could be found
I've tried everything under the sun, including trying a concrete type as opposed to anonymous type, declaring the #Model line at the top of the view file but no luck. I'm wondering if the library is at fault or definately me?
By the way, the razorengine I am referring to is available here at codeplex:
RazorEngine
If you make the call like so:
Razor.Parse(System.IO.File.ReadAllText(YourPath),
new { ActivationLink = activationLink });
That should give you the correct output. But after I see your method posted above I'll be able to make a determination where the problem lies.
Update
Change your method to the following:
public class RazorEngineRender {
public static string RenderPartialViewToString<T>(string templatePath, string viewName, T model) {
string text = System.IO.File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Razor.Parse(text, model);
return renderedText;
}
}
and you can call it like you do above.
The reason it doesn't work is because you're telling the Parser that the model is of type object rather than passing in what type it really is. In this case an anonymous type.
The accepted answer was perfect in 2011 (I believe pre-v3 of RazorEngine) but this code is now marked as obsolete in latest version (in time of typing it is 3.7.3).
For newer version your method can be typed like this:
public static string RenderPartialViewToString<T>(string templatePath, string templateName, string viewName, T model)
{
string template = File.ReadAllText(Path.Combine(templatePath, viewName));
string renderedText = Engine.Razor.RunCompile(template, templateName, typeof(T), model);
return renderedText;
}
and in order for it to work you need to add
using RazorEngine.Templating;
Here are a few hints you might try:
Make your razor view strongly typed to a model:
#model Foo
<div>
<div>
Link:
<a href="#Model.ActivationLink" style="color:#666" target="_blank">
#Model.ActivationLink
</a>
</div>
</div>
When rendering it pass a Foo model:
_emailService.Render(
TemplatePath,
"Email.cshtml",
new Foo { ActivationLink = activationLink }
)
If you are trying to send emails from your Views make sure you checkout Postal before reinventing something.