ASP MVC ActionLinks with HTML content - asp.net-mvc-4

There are nice extension methods to generate ActionLinks/RouteLinks in ASP MVC. However it only lets you write plain text inside the generated tag. What if you want to generate anchor with image inside?
I wanted to create links using the icons from bootstrap:
// expected result
<i class="glyphicon glyphicon-arrow-left"></i> Previous page
When you want to generate simple link, you can use #Url.Action() like this:
<i class="glyphicon glyphicon-arrow-left"></i> Previous page
But when you want to generate ajax links, it's not so simple. Because #Ajax.ActionLink generates anchor with javascript or 'data-*' atributes that are handled by jquery-unobtrusive-ajax-min.js library.

So I wrote for my purpose extension methods to generate ActionLinks/RouteLinks in way you use #Html.BeginForm/#Ajax.BeginForm (surrounded by using).
Usage:
// instead
#Html.ActionLink("Previous page", "Index", "Customer", new { page = 1 })
// you can write
#using(Html.BeginActionLink("Index", "Customer", new { page = 1 }) {
<text><i class="glyphicon glyphicon-arrow-left"></i> Previous page</text>
}
// same with ajax links
#using(Ajax.BeginActionLink("Index", new { page = 1 }, new AjaxOptions { ... }) {
<text><i class="glyphicon glyphicon-arrow-left"></i> Previous page</text>
}
Methods BeginActionLink return instance of class MvcLink which implements IDisposable. In constructor it writes start tag and when disposed it writes end tag. Between curly brackets there is place for your code
namespace System.Web.Mvc
{
using System.Text.RegularExpressions;
public class MvcLink : IDisposable
{
internal static readonly string InnerText = "___F7ED35E0097945398D5A969F8DE2C63C___";
private static readonly Regex RegexPattern = new Regex(#"^\s*(?<startTag>.*)\s*" + InnerText + #"\s*(?<endTag>.*)\s*$", RegexOptions.Compiled | RegexOptions.Singleline);
private readonly ViewContext _viewContext;
private readonly string _endTag;
internal MvcLink(ViewContext viewContext, IHtmlString actionLink) {
_viewContext = viewContext;
var match = RegexPattern.Match(actionLink.ToHtmlString());
if (match.Success) {
var startTag = match.Groups["startTag"].Value;
_endTag = match.Groups["endTag"].Value;
_viewContext.Writer.Write(startTag);
}
}
public void Dispose() {
_viewContext.Writer.Write(_endTag);
}
}
}
Then it's up to you to write extension methods for HtmlHelper and AjaxHelper. There are too many overloads for method ActionLink/RouteLink so I prepared just those that I realy use in my application.
But it's easy to write others. You can see that I create instance of MvcLink, it takes ViewContext as first parameter and the result of builtin ActionLink with predefined InnerText that will be replaced by your content.
namespace System.Web.Mvc
{
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
public static class MvcHelperExtensions
{
public static MvcLink BeginActionLink(this AjaxHelper ajaxHelper, string actionName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes) {
return new MvcLink(ajaxHelper.ViewContext, ajaxHelper.ActionLink(MvcLink.InnerText, actionName, routeValues, ajaxOptions, htmlAttributes));
}
public static MvcLink BeginActionLink(this HtmlHelper htmlHelper, string actionName, object routeValues, object htmlAttributes) {
return new MvcLink(htmlHelper.ViewContext, htmlHelper.ActionLink(MvcLink.InnerText, actionName, routeValues, htmlAttributes));
}
}
}

Related

Using URL path for localization in Razor + Blazor components

I want to build an ASP.NET Razor app with razor pages and some Blazor components, with site content being localized based on the language in the URL.
For example, /en/home and /fr/home would have one backing page that renders content based on the language.
What's a method to accomplish this?
AspNetCore.Mvc.Localization has what we need.
Inside _ViewImports.cshtml, we can inject an IViewLocalizer which will grab .resx files for the corresponding pages.
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
Now the Localizer is available inside all our pages.
For example, Index.cshtml
#page
#model IndexModel
#{
ViewData["Title"] = #Localizer["Title"];
}
<h1>#Localizer["Header"]</h1>
<section>
<p>#Localizer["Welcome", User.Identity.Name]</p>
#Localizer["Learn"]
<a asp-page="Page1">#Localizer["SomePage"]</a>
<a asp-page="Dogs/Index">#Localizer["LinkDogs"]</a>
</section>
Now the page title, header, and content is localized once the resx files are created.
Resources/Pages/Index.resx and Resources/Pages/Index.fr.resx needs to be created. There is a VSCode extension available for this since these files are just ugly XML.
Strings can be parameterized. In the Index.cshtml example, "Welcome"="Howdy {0}" gets referenced by #Localizer["Welcome", User.Identity.Name] and the username will be substituted in for {0}.
Inside Startup.cs, we also need to add some setup.
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
}); // new
services.AddRazorPages()
.AddRazorRuntimeCompilation()
.AddViewLocalization(); // new
services.AddServerSideBlazor();
But this only gives access to the Localizer inside our .cshtml files. Our pages still look like /home instead of /en/home.
To fix this, we will add an IPageRouteModelConvention to modify our page templates, prepending {culture} to all our pages.
Inside Startup.cs, we need to add the convention during razor config.
services.AddRazorPages(options =>
{
options.Conventions.Add(new CultureTemplatePageRouteModelConvention());
})
I created the CultureTemplatePageRouteModelConvention.cs under a Middleware/ folder, but you can put it wherever (not sure if it's "technically" middleware?).
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.Logging;
namespace app.Middleware
{
public class CultureTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
// For each page Razor has detected
foreach (var selector in model.Selectors)
{
// Grab the template string
var template = selector.AttributeRouteModel.Template;
// Skip the MicrosoftIdentity pages
if (template.StartsWith("MicrosoftIdentity")) continue;
// Prepend the /{culture?}/ route value to allow for route-based localization
selector.AttributeRouteModel.Template = AttributeRouteModel.CombineTemplates("{culture?}", template);
}
}
}
}
Now going to /en/home should resolve, and /home should not.
But if you go to /fr/home you will notice that it's still using the English resx file. This is because the culture is not being updated based on the URL.
To fix this, more modifications to Startup.cs are necessary.
In the Configure method, we will add
app.UseRequestLocalization();
Under ConfigureServices, we will configure the request localization options.
This will include adding a RequestCultureProvider which is used to determine the Culture for each request.
services.Configure<RequestLocalizationOptions>(options =>
{
options.SetDefaultCulture("en");
options.AddSupportedCultures("en", "fr");
options.AddSupportedUICultures("en", "fr");
options.FallBackToParentCultures = true;
options.RequestCultureProviders.Remove(typeof(AcceptLanguageHeaderRequestCultureProvider));
options.RequestCultureProviders.Insert(0, new Middleware.RouteDataRequestCultureProvider() { Options = options });
});
This uses an extension method to remove the default accept-language header culture provider
using System;
using System.Collections.Generic;
using System.Linq;
namespace app.Extensions
{
public static class ListExtensions {
public static void Remove<T>(this IList<T> list, Type type)
{
var items = list.Where(x => x.GetType() == type).ToList();
items.ForEach(x => list.Remove(x));
}
}
}
More importantly, we need to create the RouteDataRequestCultureProvider we just added to the list.
Middleware/RouteDataRequestCultureProvider.cs
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Middleware
{
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
string routeCulture = (string)httpContext.Request.RouteValues["culture"];
string urlCulture = httpContext.Request.Path.Value.Split('/')[1];
// Culture provided in route values
if (IsSupportedCulture(routeCulture))
{
return Task.FromResult(new ProviderCultureResult(routeCulture));
}
// Culture provided in URL
else if (IsSupportedCulture(urlCulture))
{
return Task.FromResult(new ProviderCultureResult(urlCulture));
}
else
// Use default culture
{
return Task.FromResult(new ProviderCultureResult(DefaultCulture));
}
}
/**
* Culture must be in the list of supported cultures
*/
private bool IsSupportedCulture(string lang) =>
!string.IsNullOrEmpty(lang)
&& Options.SupportedCultures.Any(x =>
x.TwoLetterISOLanguageName.Equals(
lang,
StringComparison.InvariantCultureIgnoreCase
)
);
private string DefaultCulture => Options.DefaultRequestCulture.Culture.TwoLetterISOLanguageName;
}
}
Note we check for RouteValues["culture"] in this provider, when that value isn't actually present yet. This is because we need another piece of middleware for Blazor to work properly. But for now, at least our pages will have the correct culture applied from the URL, which will allow /fr/ to use the correct Index.fr.resx instead of Index.resx.
Another issue is that the asp-page tag helper doesn't work unless you also specify asp-route-culture with the user's current culture. This sucks, so we will override the tag helper with one that just copies the culture every time.
Inside _ViewImports.cshtml
#* Override anchor tag helpers with our own to ensure URL culture is persisted *#
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper *, app
and under TagHelpders/CultureAnchorTagHelper.cs we will add
using System;
using app.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
// https://stackoverflow.com/a/59283426/11141271
// https://stackoverflow.com/questions/60397920/razorpages-anchortaghelper-does-not-remove-index-from-href
// https://talagozis.com/en/asp-net-core/razor-pages-localisation-seo-friendly-urls
namespace app.TagHelpers
{
[HtmlTargetElement("a", Attributes = ActionAttributeName)]
[HtmlTargetElement("a", Attributes = ControllerAttributeName)]
[HtmlTargetElement("a", Attributes = AreaAttributeName)]
[HtmlTargetElement("a", Attributes = PageAttributeName)]
[HtmlTargetElement("a", Attributes = PageHandlerAttributeName)]
[HtmlTargetElement("a", Attributes = FragmentAttributeName)]
[HtmlTargetElement("a", Attributes = HostAttributeName)]
[HtmlTargetElement("a", Attributes = ProtocolAttributeName)]
[HtmlTargetElement("a", Attributes = RouteAttributeName)]
[HtmlTargetElement("a", Attributes = RouteValuesDictionaryName)]
[HtmlTargetElement("a", Attributes = RouteValuesPrefix + "*")]
public class CultureAnchorTagHelper : AnchorTagHelper
{
private const string ActionAttributeName = "asp-action";
private const string ControllerAttributeName = "asp-controller";
private const string AreaAttributeName = "asp-area";
private const string PageAttributeName = "asp-page";
private const string PageHandlerAttributeName = "asp-page-handler";
private const string FragmentAttributeName = "asp-fragment";
private const string HostAttributeName = "asp-host";
private const string ProtocolAttributeName = "asp-protocol";
private const string RouteAttributeName = "asp-route";
private const string RouteValuesDictionaryName = "asp-all-route-data";
private const string RouteValuesPrefix = "asp-route-";
private readonly IHttpContextAccessor _contextAccessor;
public CultureAnchorTagHelper(IHttpContextAccessor contextAccessor, IHtmlGenerator generator) :
base(generator)
{
this._contextAccessor = contextAccessor;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var culture = _contextAccessor.HttpContext.Request.GetCulture();
RouteValues["culture"] = culture;
base.Process(context, output);
}
}
}
This uses an extension method to get the current culture from an HttpRequest
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Extensions
{
public static class HttpRequestExtensions
{
public static string GetCulture(this HttpRequest request)
{
return request.HttpContext.Features.Get<IRequestCultureFeature>()
.RequestCulture.Culture.TwoLetterISOLanguageName;
}
}
}
To make sure the dependency injection for the current context works, we need to modify Startup.cs
// Used by the culture anchor tag helper
services.AddHttpContextAccessor();
Now we can use the tag helper without things breaking.
Example:
<a asp-page="Page1">#Localizer["SomePage"]</a>
With normal pages working, now we can work on getting Blazor components translated.
Inside _Imports.razor, we will add
#using Microsoft.Extensions.Localization
Inside our myComponent.razor, we will add
#inject IStringLocalizer<myComponent> Localizer
Now we can use <h1>#Localizer["Header"]</h1> just like in our normal pages. But now there's another issue: our Blazor components aren't getting their Culture set correctly. The components see /_blazor as their URL instead of the page's URL. Comment out the <base href="~/"> in your <head> element in _Layout.cshtml to make Blazor try hitting /en/_blazor instead of /_blazor. This will get a 404, but we will fix that.
Inside Startup.cs, we will register another middleware.
app.Use(new BlazorCultureExtractor().Handle);
This call should be before the app.UseEndpoints and app.UseRequestLocalization() call.
Middleware/BlazorCultureExtractor.cs
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using app.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
namespace app.Middleware
{
public class BlazorCultureExtractor
{
private readonly Regex BlazorRequestPattern = new Regex("^/(.*?)(/_blazor.*)$");
public async Task Handle(HttpContext context, Func<Task> next)
{
var match = BlazorRequestPattern.Match(context.Request.Path.Value);
// If it's a request for a blazor endpoint
if (match.Success)
{
// Grab the culture from the URL and store it in RouteValues
// This allows IStringLocalizers to use the correct culture in Blazor components
context.Request.RouteValues["culture"] = match.Groups[1].Value;
// Remove the /culture/ from the URL so that Blazor works properly
context.Request.Path = match.Groups[2].Value;
}
await next();
}
}
}
The middleware will check if the route is trying to hit /en/_blazor, will set the RouteValues["culture"] value to en, and will rewrite the path to /_blazor before further processing. This puts the lang in the route values for our RequestCultureProvider to use, while also fixing the 404 from blazor trying to hit our localized routes.
Inside _Layout.cshtml I also use
<script src="~/_framework/blazor.server.js"></script>"
to ensure that the request for the blazor script hits the proper path instead of /en/_framework/.... Note the preceeding ~/ on the src attribute.
Closing remarks
If you want pure URL-based localization instead of the weird cookie stuff MS promotes, then it's a lot of work.
I haven't bothered looking into doing this with Blazor pages, I'm just sticking with components for now.
e.g.,
<component>
#(await Html.RenderComponentAsync<MyCounterComponent>(RenderMode.Server))
</component>

How to generate a Razor Page url within a custom TagHelper

I have a custom tag helper which should render something like this:
<ol>
<li>Some text
</ol>
If I were to do this within a Razor Page I would simply do something like this: <a asp-page="MyRazorPage">Some text</a>
Is there a way to do something similar inside of the TagHelper?
I found the answer.
Inject IUrlHelperFactory into the constructor as well as use the following property:
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
Then you can create an IUrlHelper this way:
var urlHelper = _urlHelperFactory.GetUrlHelper(ViewContext);
var url = urlHelper.Page("/Clients/Edit", new { Id = myClientId });
output.Content.AppendHtmlLine($"<a href='{url}'>Edit</a>");
TagHelper provides HtmlTargetElement to add attributes to specified tags. Take adding asp-cuspage to the tag <a> as an example. The method Init is used to receive the parameters in the instruction asp-cuspage="". This method Process provides output attributes.
Create class CusAnchorTagHelper:
[HtmlTargetElement("a")]
public class CusAnchorTagHelper : TagHelper
{
private const string CuspageAttributeName = "asp-cuspage";
[HtmlAttributeName(CuspageAttributeName)]
public string Cuspage { get; set; }
public string Value { get; set; }
public override void Init(TagHelperContext context)
{
if (context.AllAttributes[0].Value != null)
{
Value = context.AllAttributes[0].Value.ToString();
}
base.Init(context);
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var reg = new Regex("(?<!^)(?=[A-Z])");
string attr="";
foreach(var a in reg.Split(Value))
{
attr += a + "/";
}
output.Attributes.SetAttribute("href", attr);
}
}
Then, inject custom taghelper assembly into the page. And it will be drawn in the view.
This is the rendered result.

ASP.NET Core- Is there a way to render group of elements each using custom tag-helpers?

I noticed in my project that all my form fields follow the same pattern. A typical example is:
<div class="col-x-x">
<label asp-for="Property"></label>
<span message="description">
<input asp-for="Property" />
<span asp-validation-for="Property"></span>
</div>
I would love to have some way of grouping this code so that i simply pass it the property on the model and it outputs the correct HTML. e.g.:
<form-field for="Property" ...>
or
#Html.StringFormField(...)
The issue I am having is that whatever method I try, the html outputted is the original html above, and not the html that is generated from the tag helpers. I have tried both methods and neither have been successful. Additionally I have tried to create a razor function, but all my attempts fail to compile, and I can't make a partial view work as I haven't been able to find a way to get the property information after passing a string to a view.
My latest attempt was using a tag helper, however this had the same issue mentioned previously. The latest version of the code is as follows:
[HtmlTargetElement("form-field", Attributes = "for")]
public class FormFieldTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
var contentBuilder = new HtmlContentBuilder();
contentBuilder.AppendHtmlLine($"<label asp-for=\"{For}\"></label>");
contentBuilder.AppendHtmlLine($"<span message=\"description.\"></span>");
contentBuilder.AppendHtmlLine($"<input asp-for=\"{For}\"/>");
contentBuilder.AppendHtmlLine($"<span asp-validation-for=\"{For}\"/></span>");
output.Content.SetHtmlContent(contentBuilder);
}
}
There is an issue addressing this (with no solution) which suggested the order of the imports was a potential issue, so my imports are as follows:
#addTagHelper Project.Web.Features.Shared.*, Project.Web
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Any solution would be welcome, either for a tag helper or another method.
You could use IHtmlGenerator to generate these elements, refer to my below demo code:
[HtmlTargetElement("form-field", Attributes = "for")]
public class FormFieldTagHelper : TagHelper
{
[HtmlAttributeName("for")]
public ModelExpression For { get; set; }
private readonly IHtmlGenerator _generator;
[ViewContext]
public ViewContext ViewContext { get; set; }
public FormFieldTagHelper(IHtmlGenerator generator)
{
_generator = generator;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
using (var writer = new StringWriter())
{
writer.Write(#"<div class=""form-group"">");
var label = _generator.GenerateLabel(
ViewContext,
For.ModelExplorer,
For.Name, null,
new { #class = "control-label" });
label.WriteTo(writer, NullHtmlEncoder.Default);
writer.Write(#"<span message=""description.""></span>");
var textArea = _generator.GenerateTextBox(ViewContext,
For.ModelExplorer,
For.Name,
For.Model,
null,
new { #class = "form-control" });
textArea.WriteTo(writer, NullHtmlEncoder.Default);
var validationMsg = _generator.GenerateValidationMessage(
ViewContext,
For.ModelExplorer,
For.Name,
null,
ViewContext.ValidationMessageElement,
new { #class = "text-danger" });
validationMsg.WriteTo(writer, NullHtmlEncoder.Default);
writer.Write(#"</div>");
output.Content.SetHtmlContent(writer.ToString());
}
}
}
View:
<form-field for="ManagerName"></form-field>
Result:
It seems the easiest way to do this without duplicating custom tag helper code with the html generator is by simply creating new instances of the custom tag helpers from within a new tag helper.
e.g.
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
//create label tag
LabelForTagHelper labelTagHelper = new LabelForTagHelper(ValidatorFactory)
{
For = this.For,
IgnoreRequired = this.IgnoreRequired
};
TagHelperOutput labelOutput = new TagHelperOutput(
tagName: tagName,
attributes: attributes ?? new TagHelperAttributeList(),
getChildContentAsync: (s, t) =>
{
return Task.Factory.StartNew<TagHelperContent>(() => new DefaultTagHelperContent());
}
);
var labelElement = await labelTagHelper.ProcessAsync(context, labelOutput);
output.Content.AppendHtml(labelElement );
//repeat for other tags
}

ASP.NET Core Integration Test for controller action

Microsoft documentation (https://learn.microsoft.com/en-us/aspnet/core/testing/integration-testing) explain how to implement an integration test using the TestServer class. It is easy in case we are using WEB API because we get the serialized model as response from the action.
But in case I want to test a Controller action returning an HTML View containing some data, how can I evaluate that the page content is what I expect (avoiding to scan the HTML page contents) ?
One option is to use Automated UI Testing using something like Selenium
In order to append this JSON serialized view model to your page, I implemented the following filter:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
using Ticketino.Web.Components.Extensions.Request;
using Ticketino.Web.OnlineShop.Serializations;
using Ticketino.Web.OnlineShop.ViewModels.Base;
namespace Ticketino.Web.OnlineShop.Filters
{
/// <summary>
/// This is a filter used only for integration tests.
/// It format the ViewModel as jSon and appends it to the end of HMTL page, so that it can be deserialized from the test in order to check its values.
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Mvc.Filters.ResultFilterAttribute" />
[AttributeUsage(AttributeTargets.Method)]
public class IntegrationTestFilterAttribute : ResultFilterAttribute
{
public const string StartViewModelContainer = "<script type=\"model/json\">";
public const string EndViewModelContainer = "</script>";
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model is BaseViewModel)
{
var errors = IntegrationTestFilterAttribute.GetModelErrors(filterContext.ModelState);
((BaseViewModel)viewResult.Model).ValidationErrors = errors;
}
}
base.OnResultExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult?.Model != null)
{
var jsonViewModel = string.Concat(
IntegrationTestFilterAttribute.StartViewModelContainer,
JsonConvert.SerializeObject(viewResult.Model, Formatting.None, CommonJsonSerializerSettings.Settings()),
IntegrationTestFilterAttribute.EndViewModelContainer);
filterContext.HttpContext.Response.WriteAsync(jsonViewModel);
}
}
base.OnResultExecuted(filterContext);
}
#region Private methods
private static IDictionary<string, string> GetModelErrors(ModelStateDictionary errDictionary)
{
var errors = new Dictionary<string, string>();
//get all entries from the ModelStateDictionary that have any errors and add them to our Dictionary
errDictionary.Where(k => k.Value.Errors.Count > 0).ForEach(i =>
{
foreach (var errorMessage in i.Value.Errors.Select(e => e.ErrorMessage))
{
errors.Add(i.Key, errorMessage);
}
});
return errors;
}
#endregion
}
}
Then, in ConfigureServices(IServiceCollection serviceCollection) method inject it when you run integration test as show:
// Filter to append json serialized view model to buttom html response page, in order to eveluate from integration test class
if (_hostingEnvironment.IsIntegrationTest())
{
mvcBuilder.AddMvcOptions(opt => { opt.Filters.Add(new IntegrationTestFilterAttribute()); });
}

Get DisplayName for Model from ViewBag

I'm using localization for my ASP.NET Page with a database.
Everything is working fine, a language Dictionary is in the ViewBag, and it's just working for everything, except the DisplayName in the ViewModels.
This is how I'm using translation in the Views:
namespace Helpers
{
public static class LocalizationHelper
{
/// <summary>
/// Returns the translation for the term
/// </summary>
public static string Translate(this HtmlHelper helper, Term term)
{
Dictionary<string, string> dic = helper.ViewContext.Controller.ViewBag.LangDict;
Dictionary<string, string> fallbackDic = helper.ViewContext.Controller.ViewBag.StandardLangDict;
string trans = "";
if (dic.TryGetValue(term.ToString(), out trans))
return trans;
else if (fallbackDic.TryGetValue(term.ToString(), out trans))
{
return trans;
//exception trans not available for this lang
//return fallback language
}
else
return "Translation not set";
}
}
But I cant use this for the ViewModel because I'cant pass the HTMLHelper Attribute.
And if i decide to write another method like:
public static string Translate(Term term)
The Viewbag cant work any more. Term is an Enumeration.
Now I have no idea how to get the Data from the Viewbag into the ViewModel.