TagHelper PreContent and PostContent not showing up in HTML when overriding InputTagHelper - asp.net-core

I want to create a tag helper to put some HTML before and after <input> tags, but I want to keep the default asp-for behavior and access the ModelExpression data.
To this end, I tried to override the Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper type, as described in this post. However, even though I can verify in the debugger that the Process method is being called PreContent and PostContent are both being set, nothing shows up in the HTML other than the standard <input> tag. It works fine when creating a tag helper from scratch for another tag though.
I created a small project to demonstrate this issue. I put the entire project on GitHub, and I'm copying the specific tag helper I'm trying to create below.
[HtmlTargetElement("input", Attributes = "asp-for,test-label")]
public class TestTagHelper : InputTagHelper
{
public TestTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("test-label")]
public string Label { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.PreContent.SetHtmlContent($"<b>{WebUtility.HtmlEncode(Label)}</b> ");
output.PostContent.SetHtmlContent($" <i>({WebUtility.HtmlEncode(For.Name)})</i>"); // access information from the input tag
base.Process(context, output);
}
}
Am I missing something obvious? Is there a caveat to doing this? Or is this something that just can't be done?

For this issue, we could try to call PreElement and PostElement instead of PreContent and PostContent like
[HtmlTargetElement("input", Attributes = "asp-for,test-label")]
public class TestTagHelper : InputTagHelper
{
public TestTagHelper(IHtmlGenerator generator) : base(generator)
{
}
[HtmlAttributeName("test-label")]
public string Label { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
output.PreElement.SetHtmlContent($"<b>{WebUtility.HtmlEncode(Label)}</b>");
output.PostElement.SetHtmlContent($"<i>({WebUtility.HtmlEncode(For.Name)})</i>");
}
}

Related

Blazor [Parameter] not update using #ref

I'm using .net6.0 Blazor razor library to create a component.
#using Microsoft.JSInterop;
#inject IJSRuntime _jsRuntime;
<audio id="#Id" src="#Source" />
#code {
[Parameter]
public string Id { get; set; }
[Parameter]
public string Source { get; set; }
}
I use this component in a razor page using this :
<AudioPlayer
Id="reactorAudioElement1"
Source="/audio/lion-roaring.ogg">
</AudioPlayer>
And everything is doing fine at this point.
But, if I try to use #ref like that,
<AudioPlayer
#ref=#_Jukebox2>
</AudioPlayer>
#code {
private AudioPlayer _Jukebox2;
protected override void OnInitialized()
{
_Jukebox2 = new AudioPlayer()
{
Id="reactorAudioElement2",
Source="/audio/Bleep_02.ogg"
};
}
}
nothing is set in the DOM.
But I can read data like this, and I cannot set it...
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
</AudioPlayer>
#code {
private AudioPlayer _Jukebox2;
protected override void OnAfterRender(bool firstRender)
{
Console.WriteLine(_Jukebox2.Id); //ok
_Jukebox2.Source = "toto.mp3"; //doesn't do anything
}
}
Adding a StateHasChanged(); is not working
What am I doing wrong ?
Your component in DOM is created with this section of code, and your component will have valid #ref value
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
</AudioPlayer>
but you are creating another object in code with this section of code
_Jukebox2 = new AudioPlayer()
{
Id="reactorAudioElement2",
Source="/audio/Bleep_02.ogg"
};
In above code you can create object, but can not have DOM reference
This part of code will create an element in DOM and set it's reference to _Jukebox2, will also set value of Source
<AudioPlayer
#ref=#_Jukebox2
Id="reactorAudioElement2"
Source="#source"
</AudioPlayer>
private string source="Source="/audio/Bleep_02.ogg"`;
And in code behind you should be able to access public methods or variable in _Jukebox2 like this
_Jukebox.Source="toto.mp3";
If its not updating DOM then issue might be somewhere else

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.

How to process tag helper in asp.net core?

I want to know in ASP.NET Core 2.2 if there is a way to invoke TagHelper through code? I have custom TagHelper
public class EmailTagHelper : TagHelper
{
public string MailTo { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
output.Attributes.SetAttribute("href", "mailto:" + MailTo);
output.Content.SetContent(MailTo);
}
}
Then in some render method in another class i want to use TagHelper to get corresponding markup
public override void Render(string email)
{
var emailTagHelper = new EmailTagHelper();
emailTagHelper.MailTo = email;
// How do i pass TagHelperContext and TagHelperOutput
emailTahHelper.Process(........);
//How do i get html string here
}
How do i process TagHelper though code here? Where would i get TagHelperContext and TagHelperOutput parameters and what method i need to invoke to get final html string?
I solved my issue by using TagBuilder instead of TagHelper.

Pass Url Parameters to Action by Model in ASP.NET MVC 4

I want to assign my url parameters to Model properties, passed as a parameter to the associated Action. For example;
Say, my url is http://www.example.com/Item/Index?color=red&size=50
My action inside the controller is like below:
public class ItemController : Controller
{
public ActionResult Index(MyModel myModel)
{
//
return View(myModel);
}
}
I want to configure the model or whatever necessary so that my model takes the color and size as field values. The following didn't work:
public class MyModel
{
[Display(Name = "color")]
public string Color{ get; set; }
[Display(Name = "size")]
public string Size{ get; set; }
}
What would be the correct way to solve the problem?
Thanks for any suggestion.
Update
Well, yes! The code above would work correctly, because Url parameter names are the same as model property names. I should explain my problem exactly as I encounter for the next time, sorry.
I must correct a part of my question to make it clear. The url should have been: http://www.example.com/Item/Index?c=red&s=50 to detect the problem.
If the url is like that, the code would not work. Because Url parameters don't have the same name as Model properties.
Updated model is below:
public class MyModel
{
[Display(Name = "c")]
public string Color{ get; set; }
[Display(Name = "s")]
public string Size{ get; set; }
}
Try adding [FromUri] in front of the parameter.
public class ItemController : Controller
{
public ActionResult Index([FromUri] MyModel myModel)
{
// do something
return View();
}
}
debugging the issue
Here are some suggestions in debugging the issue, as it should work out of the box.
try binding to primitive types
public class ItemController : Controller
{
public ActionResult Index(string color, string size)
{
// do something
return View();
}
}
Try reading out of the request object directly
var size = this.Request["size"];
If either of those work there is an issue with your model binding.
Update
If you want to have the query string parameters different to the model in MVC you'll need to have a custom model binder. Take a look at Asp.Net MVC 2 - Bind a model's property to a different named value and http://ole.michelsen.dk/blog/bind-a-model-property-to-a-different-named-query-string-field.html which extends the answer a little.
https://github.com/yusufuzun/so-view-model-bind-20869735 has an example with some html helpers that could be useful.

Why does ASP.NET MVC assumes that view will have matching input and output types?

ASP.NET MVC (or rather Html.Helpers and base page implementation) assumes that there will be one type for both rendering and posting (namely Model).
This is a violation of ISP, isn't it?
I am tempted to derive my Edit views (those that have different render-data, and post-data) from a custom EditPageBaseView<TViewModel, TFormData>.
The problem is I want my validation and post work against FormData instance (stored inside ViewModel), but MVC assumes that entire ViewModel will be POSTed back.
Is there an OOB way to facilitate that? (I didn't find one if there is).
Is it a bad idea (in concept) to have separate data types for different operations exposed by a service (a view in this case).
I tend to follow the CQRS model when constructing my view models. All rendering is done with ViewModel classes and all posting back is done with Command classes. Here's a contrived example. Let's say we have a View with a small form for creating users.
The ViewModel and Command classes looks like this:
public abstract class ViewModel {}
public abstract class Command: ViewModel
public class CreateUserViewModel : ViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
public class CreateUserCommand : Command
{
public string Username { get; set; }
public string Password { get; set; }
public string PasswordConfirm { get; set; }
}
The UserController creates a CreateUserViewModel as the model for the Get request and expects a CreateUserCommand for the Post request:
public ActionResult CreateUser()
{
// this should be created by a factory of some sort that is injected in
var model = new CreateUserViewModel();
return View(model);
}
[HttpPost]
public ActionResult CreateUser(CreateUserCommand command)
{
// validate and then save the user, create new CreateUserViewModel and re-display the view if validation fails
}
Model binding takes care of ensuring that the properties of the Posted CreateUserCommand are populated properly, even though the Get View is bound to a CreateUserViewModel.
They don't have to match, but they do match by default.
If you don't want them to match, you can specify a different model in your Form or ActionLink:
Example of a Mismatch using Razor and C#:
Index.chtml:
#model FirstModel
<div>
#using (Html.BeginForm("Action", "ControllerName", new { ParameterName = new SecondModel { First = "First", Second = "Second" } }, FormMethod.Post)) {
<input type="submit" value="Submit Button" />
}
</div>
The Controller:
public class ControllerName : Controller {
public ActionResult Index() {
return View(new FirstModel());
}
public ActionResult Action(SecondModel ParameterName) {
return View() // Where to now?
}