Custom Templated WebControls - Inner Contols are always null - custom-controls

I'm building a templated web control for an asp .net web forms application. Most of it runs fine, but one problem I’m stuck with is that all the controls inside my template are null at runtime.
For instance if my control is marked up like this:
<fscc:CollapsiblePanel runat="server" ID="cpExample1" ImageControlId="image1" CssClass="curved">
<Header>
this is my header
<asp:Image ImageUrl="imageurl" runat="server" ID="image1" />
</Header>
<Body>
this is the body
</Body>
</fscc:CollapsiblePanel>
And my page load is like this:
protected void Page_Load(object sender, EventArgs e)
{
image1.ImageUrl = Paths.Images.expand_jpg;
}
It will compile and run, but image1 is always null.
I have added these attributes to the class:
[ParseChildren(true), PersistChildren(false)]
public class MyControl : WebControl
And to the template property I have added these:
[Browsable(false),
PersistenceMode(PersistenceMode.InnerProperty),
TemplateInstance(TemplateInstance.Single)]
public virtual ITemplate Header { get; set; }
So far as I can tell this is enough for the image1 control to be available. Also the tooling seems to recognise control and puts the image1 into the .designer.cs file, but it is always null. What am I missing?

Related

How to pass ViewModels into Razor Components in .NET Core 3.1

I have a View MyView.cshtml with the following content:
#using MyProject.ViewModels
#model MyProject.ViewModels.MyViewViewModel
<form asp-action="Test" method="Post">
<component type="typeof(MyProject.Views.Home.Test)" render-mode="ServerPrerendered" />
<input type="submit" value="send"/>
</form>
And I have the Razor Component Test.razor with the following content (with Blazor Syntax):
#page "/Test"
<div class="form-group top-buffer #Visible">
<div class="row">
<div class="col-2">
<label asp-for="TestName" class="control-label"></label>
</div>
<div class="col-3">
<input asp-for="TestName" class="form-control" />
<span asp-validation-for="TestName" class="text-danger"></span>
</div>
</div>
</div>
<button #onclick="Show">Show</button>
#code {
public string Visible { get; set; } = "hidden";
protected async Task Show()
{
Visible = "";
}
}
The Class MyViewViewModel would look like this:
namespace MyProject.ViewModels
{
public class MyViewViewModel
{
[Display(Name = "Test Name:")]
public string TestName { get; set; }
}
}
Works all pretty fine so far. However I now want to use this component as part of a Web form which will be sent to the controller after submission. That's why I need to access and change properties of my ViewModel 'MyViewViewModel'. Unfortunately I did not find any answer in the internet on how to do that. I can't use #model MyProject.ViewModels.MyViewViewModel like in the view because this will give me a compilation error. I wonder if I need to use #inject, but if yes, I don't know how...
(parts are used from this example: https://jonhilton.net/use-blazor-in-existing-app/)
When you mix Blazor in a Razor Page, you can do the following:
Render a Razor Component
Interact with a Razor Component
Pass a Razor Component values
Please keep in mind that you are dealing with two different life-cycles. So if you do work inside of a Razor Component, the component will update but not effect the Razor Page it is hosted inside of. So mixing Razor Components and Pages with forms would be difficult.
More specifically to the OP. To pass data from your ViewModel to the component you may use the following method.
#using MyProject.ViewModels
#model MyProject.ViewModels.MyViewViewModel
<form asp-action="Test" method="Post">
<component type="typeof(MyProject.Views.Home.Test)"
render-mode="ServerPrerendered"
param-Name="#Model.TestName"/>
<input type="submit" value="send"/>
</form>
Test.razor
<h3>HelloWorld</h3>
Hello #Name
#code {
[Parameter]
public string Name { get; set; } = "undefined";
}
About life cycles
Basically when you have a button in Blazor, it will trigger an event which causes the component to re-render. You could imagine it like an iframe, or update-panel. When you have a button in a Razor page, it does a HTTP call round trip and reloads the page entirely. There is no event system in place to tell Blazor to invoke an HTTP call round trip to refresh the Razor page's content and vise versa. You can only one-way data-bind from Razor pages to Blazor, think write-only, and only when the page loads.
To hopefully add to the info. With a ASP.Net Core MVC project host Blazor webassembly, I was trying to pass a viewmodel into a razor component using this code in my view cshtml file:
<component Type="typeof(Leave)" render-mode="WebAssembly" model="new { model = (MyViewModel)#Model})"/>
But it would fail to render the razor component if I tried to access data in the viewmodel from the razor component with an Object not set exception. I think it was accessing the data before the view model has been initialized. Maybe if I set a default value this could avoided?
I found by using this instead I was able to get it working.
#(await Html.RenderComponentAsync<Leave>(RenderMode.WebAssembly,new { model = (MyViewModel)#Model}))
Edit
Seems you also need to register the viewModel class in the services in the Blazor WASM project in the Program.cs file.
builder.Services.AddScoped(sp => new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<MyViewModel,MyViewModel>(); // <= add this line
await builder.Build().RunAsync();`
Without that I would get an error saying the property could not be found.
Hopefully this saves someone else some time :-)

How to create AJAX Form in Asp.Net Core

<form data-ajax="true" data-ajax-mode="replace" data-ajax-update="#results" asp-action="CreateCarClient" asp-controller="Account" method="post" enctype="multipart/form-data">
This form is not work
You are using jQuery Unobtrusive AJAX in ASP.NET Core.You need to install the jquery.unobtrusive-ajax package into your project using npm install jquery.unobtrusive-ajax and add references to it in your view.
See tutorials of razor pages here.
This link displays my example of how to use the code step by step.
You can use FormHelper to create ajax forms on ASP.NET Core. Also, FormHelper helps you to transform server-side validations to client-side.
It's so easy to use. You just need to add asp-formhelper="true" to your form tag.
<form asp-formhelper="true" asp-controller="Home" asp-action="Save">
// <input...
// ...
</form>
You can check it's documentation on FormHelper GitHub Page. And you can download that package from Nuget.
Here is the solution of Ajax.BeginForm as everything is available in this package
Only thing change is Html.AjaxBeginForm
PM> Install-Package AspNetCore.Unobtrusive.Ajax
Reference is here
If you attempting to do this in .NET 5 then add the JQuery.Unobtrusive.Ajax libraries to your project as you normally would, then write your own little tag helper!
This one is VERY basic but you can expand on it as you wish.
namespace MyProject.Helpers.TagHelpers
{
[HtmlTargetElement("form", Attributes ="ajax")]
public class AjaxForm : TagHelper
{
public string replaceId { get; set; }
public string onAjaxBegin { get; set; }
public string id { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagMode = TagMode.StartTagAndEndTag;
output.Attributes.SetAttribute("data-ajax", "true");
output.Attributes.SetAttribute("data-ajax-method", "POST");
output.Attributes.SetAttribute("data-ajax-mode", "replace");
output.Attributes.SetAttribute("method", "post");
output.Attributes.SetAttribute("id", id);
if (!string.IsNullOrWhiteSpace(onAjaxBegin))
output.Attributes.SetAttribute("data-ajax-begin", onAjaxBegin);
if (string.IsNullOrWhiteSpace(replaceId))
throw new Exception("ReplaceId is required!");
output.Attributes.SetAttribute("data-ajax-update", $"#{replaceId.TrimStart('#')}");
}
}
}
Remember to register this tag helper in your _ViewImports.cshtml
#addTagHelper MyProject.Helpers.TagHelpers.*, MyProject
Usage Example:
<form id="GandalfForm" ajax replace-id="partialViewWrapper" on-ajax-begin="OnBeginDoSomethingInJavascript" asp-controller="SomeController" asp-action="SomeMethod">
<div id="partialViewWrapper">
#await Html.PartialAsync("~/Views/Shared/SampleContent.cshtml", Model)
</div>
Note that the "ReplaceId" DOM element must start with a # in order for the unobtrusive ajax library to work correctly.

ASP.NET MVC3 HtmlHelper extension method like BeginForm that uses a partial view?

I created an extension method based on this answer to the SO question c# - How can I create a Html Helper like Html.BeginForm - Stack Overflow and it works fine.
Can I move the embedded HTML in the extension method into a partial view and use that partial view in the method while preserving it's current behavior? In particular, I want to be able to 'wrap' a block of arbitrary HTML.
I ask not out of any pressing need, but simply out of a desire to maintain HTML consistently, e.g. as views and partial views. I imagine it will be a lot easier to spot any problems with the HTML if it's in a view or partial view too.
Here's the HtmlHelper extension method:
public static IDisposable HelpTextMessage(this HtmlHelper helper, bool isHidden, string heading)
{
TextWriter writer = helper.ViewContext.Writer;
writer.WriteLine(
String.Format(
"<div class=\"help-text {0}\">",
isHidden ? "help-text-hidden" : ""));
writer.WriteLine(
String.Format(
"<div class=\"help-text-heading\">{0}</div>",
heading));
writer.Write("<div class=\"help-text-body\">");
return new HelpTextMessageContainer(writer);
}
Here's the HelpTextMessageContainer class:
private class HelpTextMessageContainer : IDisposable
{
private readonly TextWriter _writer;
public HelpTextMessageContainer(TextWriter writer)
{
_writer = writer;
}
public void Dispose()
{
_writer.Write("</div></div>");
}
}
In a view, I can use the extension method like this:
#using(Html.HelpTextMessage(Model.HelpText.IsHelpTextHidden(Model.SomeHelpMessage), "Something"))
{
#:To do something, first do something-more-specific, then do another-something-more-specific.
}
Or I could use it like this too:
#using(Html.HelpTextMessage(Model.HelpText.IsHelpTextHidden(Model.SomeHelpMessage), "Something"))
{
<p>To do something, first do something-more-specific, then do another-something-more-specific.</p>
<p>Also, keep in mind that you might need to do something-else-entirely if blah-blah-blah.</p>
}
I haven't found any way to move the "embedded HTML" into a partial view exactly, but a slightly more-friendly way to encapsulate the HTML in a way that provides HTML and Razor syntax highlighting and Intellisense is to move into a view helper function in a file under the app App_Code folder as described in this post on "Hugo Bonacci's Blog".
Here's my helper function:
#helper HelpTextMessage(bool isHidden, string heading, Func<object, object> content)
{
<div class="help-text #(isHidden ? "help-text-hidden" : "")">
<div class="help-text-heading">
#heading
</div>
<div class="help-text-body">
#content(null)
</div>
</div>
}
Assuming the above helper function is in a file named ViewHelpers.cshtml in the App_Code folder, it can be called in a view like this:
#ViewHelpers.HelpTextMessage(false, "Something",
#:To do something, first do something-more-specific, then do another-something-more-specific.
)
or this:
#ViewHelpers.HelpTextMessage(false, "Something",
<p>To do something, first do something-more-specific, then do another-something-more-specific.</p>
<p>Also, keep in mind that you might need to do something-else-entirely if blah-blah-blah.</p>
)
I like having the embedded HTML in a view more than I do being able to use the #using(Html.HelpTextMessage(...){ ... } syntax, so I'm pretty happy with this as a solution.

MVC 3 Razor, Helpers with custom markup/section

I'm not even sure if this is possible, but I thought I would check to see if there is any way to make this easier.
First, I have some repeated markup in my site that looks like this:
<div class="module">
<h3>Title</h3>
<div>
<p>Information goes here</p>
</div>
</div>
What I want to do is wrap this up in some kind of helper/section so that I could do something like this
#MyHelper("This is my Title") {
<p>Here is my custom markup</p>
}
Then, when it renders, it would inject the title passed in through the parameter between the <h3></h3> and the custom markup in the divs. The custom markup could be anything from test, to form controls, to a partial view. Is this something that is possible?
There's also the other way, without disposable trick, which also requires a little less work, great for little helpers.
#helper MyHelper(string title, Func<object, object> markup) {
<div class="module">
<h3>Title</h3>
<div>
<p>#markup.DynamicInvoke(this.ViewContext)</p>
</div>
</div>
}
Usage of this helper looks like this:
#MyHelper("This is my Title",
#<p>Here is my custom markup</p>
)
Or with multiple lines:
#MyHelper("This is my Title",
#<text>
<p>More than one line</p>
<p>Of markup</p>
</text>
)
Telerik MVC controls used this trick for example to let you add your javascript code at the document load.
Here's also a nice example. There's also some information here.
Well, here's a "standard" way of doing something close to it, using an HTML helper extension. A very simple version of what Html.BeginForm() does.
Approach: Simply return an IDisposable, and let the using statement take care of the rest.
This is just an example of the concept (although it works). Not intended for immediate reuse. Written quickly with lots of shortcuts, not production code, plenty of opportunities for improvement and optimization, may have silly mistakes, could use TagBuilder etc. etc. Could easily be modified to reuse the Wrapper class for different... wrappings (there may even be a generic one already in ASP.NET MVC - haven't had a need for one).
public static class WrapHelper
{
private class Wrapper : IDisposable
{
private bool disposed;
private readonly TextWriter writer;
public Wrapper(HtmlHelper html)
{
this.writer = html.ViewContext.Writer;
}
public void Dispose()
{
if (disposed) return;
disposed = true;
writer.WriteLine(" </div>");
writer.WriteLine("</div>");
}
}
public static IDisposable Wrap(this HtmlHelper html, string title)
{
TextWriter writer = html.ViewContext.Writer;
writer.WriteLine("<div class=\"module\">");
writer.WriteLine(" <h3>" + html.Encode(title) + "</h3>");
writer.WriteLine(" <div>");
return new Wrapper(html);
}
}
Usage:
#using (Html.Wrap("Title"))
{
<p>My very own markup.</p>
}
#TheKaneda, Thanks for the insight. I took your idea and extended it, such that you supply a PartialView name and it knows how to parse it.
<Extension()> _
Public Function UseTemplate(ByVal html As HtmlHelper, ByVal PartialView As String) As IDisposable
Return New TemplateWrapper(html, PartialView)
End Function
Public Class TemplateWrapper
Implements IDisposable
Private _HtmlHelper As HtmlHelper
'Index 0 is header
'Index 1 is footer
Private _TemplateWrapper As String()
Public Sub New(ByVal Html As HtmlHelper, ByVal PartialView As String)
_TemplateWrapper = Html.Partial(PartialView).ToHtmlString.Split("##RenderBody()")
_HtmlHelper = Html
_HtmlHelper.ViewContext.Writer.Write(_TemplateWrapper(0))
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
_HtmlHelper.ViewContext.Writer.Write(_TemplateWrapper(1).Substring(12))
End Sub
End Class
Use the same usage as #TheKaneda's example. In your partial view, instead of calling #RenderBody(), just put ##RenderBody() which acts as a flag for the middle part of your content. Sorry for the VB translation.
Uses an example of my usage.
Using Html.UseTemplate("ContentWrapper")
#Html.EditorFor(Function(m) m.Var1, "TemplateHint")
#Html.EditorFor(Function(m) m.Var2, "TemplateHint")
#Html.EditorFor(Function(m) m.Var3)
End Using
My Partial looks like this...
<div class="content">
##RenderBody()
</div>
If you are using Razor and MVC 3 it's real easy to write a quick helper method that would go inside the app_code folder, (I'll name it MyHtmlHelpers)
I'd try something a little different and a little easier such as:
#helper myTemplate(string title, string markup){
<div class="module">
<h3>#title</h3>
#markup
</div>
}
And the way you use it from a .cshtml file is as followed:
#MyHtmlHelpers.myTemplate('title','markup')
It should work, if I understand you correctly.

Why doesn't [Required] work for me? MVC3 [duplicate]

Ok I am near wits end here. I've got a simple MVC3 application with a viewmodel
ViewModel
public class TicketViewModel {
public Ticket Ticket { get; set; }
[DisplayName("Name")]
[Required(ErrorMessage = "Requestor's name is required.")]
public string Name { get; set; } }
Controller
[HttpPost]
public ActionResult Create(TicketViewModel vm)
{
if(ModelState.IsValid) {
TempData["message"] = "Your ticket has been submitted.";
TempData["message-class"] = "success";
return RedirectToAction("Index");
}
TempData["message-class"] = "error";
return View("Index", vm);
}
For some reason ModelState.IsValid is coming through as true all the time. Even when Name is left blank. It's like the model/viewmodel isn't validating at all. This works on other applications so I'm pretty sure I'm not hooking up something. I've got all the validation javascript included as well though I don't think that's the problem right now.
Update
Interestingly enough, the html tags that are being generated by #Html.TextBoxFor() are NOT including the data-val and data-val-required attributes.
View
#model MyApp.ViewModels.TicketViewModel
#{
ViewBag.Title = "Tickets";
}
<div id="main-content">
<section class="large">
<div class="section">
<div class="section-header">Submit Ticket</div>
<div class="section-content">
<div class="message"></div>
#using( Html.BeginForm("Create", "Home", FormMethod.Post) ) {
<h2>User Information</h2>
<dl>
<dt>#Html.LabelFor( m => m.Name)</dt>
<dd>
#Html.TextBoxFor( m => m.Name)
#Html.ValidationMessageFor( m => m.Name)
</dd>
<dt></dt>
<dd><button>Submit</button></dd>
</dl>
}
</div>
</div>
</section>
</div>
UPDATE II
Well now this is interesting. I created a fresh app and got things working with basic code. Then when I added DI code to the global.asax.cs validations stopped working. Specifically, when I add
public void SetupDependencyInjection() {
_kernel = new StandardKernel();
RegisterServices(_kernel);
DependencyResolver.SetResolver(new NinjectDependencyResolver(_kernel));
}
and call it from Application_Start()
protected void Application_Start()
{
SetupDependencyInjection();
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
if I remove SetupDependencyInjection() validations start working. To be clear, DI works well but it seems to kill validations. This worked well prior to MVC3 Tools Update.
I was able to find a solution. Seems that when you install Ninject via nuget the configuration is a little different. It configures your application from the App_Start folder. Basically I was doubling up on my Ninject-Fu calling in from global.asax. This ended up causing the weird validation issues, though the other parts of the application were working.
Ninject - Setting up an MVC3 application
Are you perhaps using something other that the default model binder (with the DI)? I'm pretty sure that the default model binder will validate an object upon binding. If you are not using the default one, you may not experience the same behavior.
Try using
#Html.EditorFor(model => model.Name)
That should apply the data- attributes correctly
I got the same error using Ninject.Mvc together with DependencyResolver. The reason was that I created a new IKernel instance for each Bootstrapper and DependencyResolver object.
//Application_Start()
DependencyResolver.SetResolver(new NinjectDependencyResolver(NinjectBooster.CreateKernel()));
To solve the problem I've changed the code to use the same cached instance, like this:
//Application_Start()
DependencyResolver.SetResolver(new NinjectDependencyResolver(NinjectBooster.GetKernel()));
...
//NinjectMVC.cs
private static IKernel _kernel;
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
public static IKernel GetKernel()
{
if (null == _kernel)
{
_kernel = new StandardKernel();
RegisterServices(_kernel);
}
return _kernel;
}

Resources