Creating a plain non-component class using a razor file - razor

Is it possible to create a plain non-component class using a razor file? The idea is to take advantage of the simplified syntax for generating RenderFragments, from a class that is not intended to be a component or a page. e.g.
#inherits object
#code {
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
where the resulting class should be equivalent to:
public class Whatever /*: object*/
{
public RenderFragment Foo => __builder =>
{
__builder.OpenElement(0, "span");
// etc
};
public RenderFragment Render(string? name) => __builder =>
{
__builder.AddContent(0, name); // or whatever
};
}
Trying exactly the above results in the generated code reporting an error:
Whatever.BuildRenderTree(RenderTreeBuilder): no suitable method to override
which is not entirely surprising, since the generated code includes an override for that method, which of course does not exist in a plain class.
I was hoping that by not including anything outside of #code the compiler would not attempt to override that method, but sadly that does not appear to be the case. Is there some other trick that can make this work?

Revised Answer
Yes you can. You can create a minimum requirement class that the Razor compiler will accept.
It doesn't need to implement IComponent as there is no intention of using it as a component.
It needs a virtual BuildRenderTree(RenderTreeBuilder builder) method for the Razor compiler to override: it's where it compiles all the markup in the main block.
public abstract class MinRazor
{
protected virtual void BuildRenderTree(RenderTreeBuilder builder) {}
}
You're Razor file then inherits from it.
RenderStuff.razor
#inherits MinRazor
#code {
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
<div class="p-2 m-2 bg-light">
<div class="p-2 m-2 bg-primary text-white">
#Message
</div>
</div>
};
}
The only baggage that the compiled code has is a blank BuildRenderTree(RenderTreeBuilder builder).
Here's the output file the compiler produces:
public partial class RenderStuff : MinRazor
{
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
}
public string Message { get; set; } = "No one set me!";
public RenderFragment GiveMeADiv => __builder =>
{
__builder.OpenElement(0, "div");
__builder.AddAttribute(1, "class", "p-2 m-2 bg-light");
__builder.OpenElement(2, "div");
__builder.AddAttribute(3, "class", "p-2 m-2 bg-primary text-white");
__builder.AddContent(4, Message);
__builder.CloseElement();
__builder.CloseElement();
};
}

Your problem comes form #inherits object.
That does indeed make a "non-component class", but why?
Just leave that out and make your helpers static:
Whatever.razor
#inherits ComponentBase
#code {
// some other stuff
internal static RenderFragment Foo => #<span class="foo"></span>;
internal static RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
And then you can use it like any static function:
#Whatever.Render("J Doe")

It appears that this is (currently at least) not possible; the closest working approach seems to be to implement it as a component anyway, but indicate that it is not intended to be used as such:
#code {
protected override void OnInitialized()
{
throw new NotSupportedException();
}
// some other stuff
public RenderFragment Foo => #<span class="foo"></span>;
public RenderFragment Render(string? name) => __builder =>
{
// some other stuff
<text>#name</text>
};
}
This unfortunately stills means that it will carry around some extra state baggage from inheriting ComponentBase, and it can't inherit any other (non-component) base class.
Making the render methods static and simply calling them from the "real" class as Henk Holterman suggests is probably the best compromise, although it would still need something like the OnInitialized override to discourage misuse.
Ideally you'd mark the static methods component as #internal too, to further reduce the scope of possible mis-use, but sadly that's not implemented yet.

Related

Blazor Server - 'Code Behind' pattern: OnInitializedAsync(): no suitable method found to override

I have a Blazor (Server) application which runs perfectly fine, and which adheres to all rules set by Microsoft.CodeAnalysis.FxCopAnalyzers and StyleCop.Analyzers.
A heavily cut-down razor page is as follows:
#inherits OwningComponentBase<MyService>
#inject IModalService ModalService
#inject IJSRuntime JSRuntime
// UI code
#code
{
private readonly CancellationTokenSource TokenSource = new CancellationTokenSource();
ElementReference myElementReferenceName;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await this.myElementReferenceName.FocusAsync(this.JSRuntime);
}
protected override async Task OnInitializedAsync()
{
....
}
public void Dispose()
{
this.TokenSource.Cancel();
}
protected void ShowModalEdit(object someObject)
{
.....
Modal.Show<MyPage>("Edit", parameters);
}
}
Note#1: I used #inherits OwningComponentBase<MyService> based on Daniel Roth's suggestion
Note#2: I am using the Chris Sainty's Modal component component
However, when I try to move all the code from the #code {...} section to a"Code Behind" partial class ("MyPage.razor.cs"), then I run into the following errors....
'MyPage' does not contain a definition for 'Service' and no accessible
extension method 'Service' accepting .....
'MyPage.OnAfterRenderAsync(bool)': no suitable method found to override
'MyPage.OnInitializedAsync()': no suitable method found to override
The type 'MyPage' cannot be used as type parameter 'T' in the generic
type or method 'IModalService.Show(string, ModalParameters,
ModalOptions)'. There is no implicit reference conversion from
'MyPage' to 'Microsoft.AspNetCore.Components.ComponentBase'.
Suggestions?
Your MyPage.razor.cs should inherit from ComponentBase class and your Mypage.razor should inherit from MyPage.razor.cs.
In your "code-behind" class you should use [Inject] attribute for every service you are injecting and make them at least protected properties to be able to use them in your razor components.
Below is an example from one of my testing apps, please note this uses .net-core 3.0, in 3.1 you can use partial classes.
Index.razor
#page "/"
#inherits IndexViewModel
<div class="row">
<div class="col-md">
#if (users == null)
{
<p><em>Hang on while we are getting data...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th class="text-danger">Id</th>
<th class="text-danger">Username</th>
<th class="text-danger">Email</th>
<th class="text-danger">FirstName</th>
<th class="text-danger">LastName</th>
</tr>
</thead>
<tbody>
#foreach (var user in users)
{
<tr>
<td>#user.Id</td>
<td>#user.Username</td>
<td>#user.Email</td>
<td>#user.FirstName</td>
<td>#user.LastName</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
IndexViewModel.cs
public class IndexViewModel : ComponentBase, IDisposable
{
#region Private Members
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private bool disposedValue = false; // To detect redundant calls
[Inject]
private IToastService ToastService { get; set; }
#endregion
#region Protected Members
protected List<User> users;
[Inject] IUsersService UsersService { get; set; }
protected string ErrorMessage { get; set; }
#endregion
#region Constructor
public IndexViewModel()
{
users = new List<User>();
}
#endregion
#region Public Methods
#endregion
#region Private Methods
protected override async Task OnInitializedAsync()
{
await GetUsers().ConfigureAwait(false);
}
private async Task GetUsers()
{
try
{
await foreach (var user in UsersService.GetAllUsers(cts.Token))
{
users.Add(user);
StateHasChanged();
}
}
catch (OperationCanceledException)
{
ShowErrorMessage($"{ nameof(GetUsers) } was canceled at user's request.", "Canceled");
}
catch (Exception ex)
{
// TODO: Log the exception and filter the exception messages which are displayed to users.
ShowErrorMessage(ex.Message);
}
}
private void ShowErrorMessage(string message, string heading ="")
{
//ErrorMessage = message;
//StateHasChanged();
ToastService.ShowError(message, heading);
}
private void ShowSuccessMessage(string message, string heading = "")
{
ToastService.ShowSuccess(message, heading);
}
protected void Cancel()
{
cts.Cancel();
}
#endregion
#region IDisposable Support
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
cts.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
TLDR
Make sure the namespace in your razor.cs file is correct
Longer explanation
In my case, I got this error when I put the class in the wrong namespace. The page.razor.cs file was in the same directory as the page.razor file and it contained a partial class as accepted by the October 2019 update.
However, even though the files were located in path/to/dir, page.razor.cs had a namespace of path.to.another.dir, which led to this error being thrown. Simply changing the namespace to path.to.dir fixed this error for me!
Came across this error when I used partial class approach and I was trying to scaffold Identity. I changed to base class aprroach it was resolved.
partial class I was using
after adding a component say MyComponent, add a class MyComponent.razor.cs
for injecting services
use
[Inject]
public BuildingServices Services { get; set; }
for base class approach
after adding a component say MyComponent, add a class MyComponent.razor.cs
change the class name and make it inherit from componentBase
MyComponentBase : ComponentBase
and place this at the top of MyComponent.razor
#inherits MyComponentBase
Use protected keyword to make your methods accessible
All the above points are very true. FWIW, and just to add a weird thing that I was finding with this issue; I could not get that error to go away. Everything was declared as it should be. Once you have ruled out all potential coding issues, I have found exiting VS, coming back in, and rebuilding clears it up. It is almost like VS just will not let go of that error.
It took two days of cleaning, rebuilding, ... and it turned out to be a cut and paste error. I copied the razor file from another, similar class, and left this at the top by mistake:
#inject Microsoft.Extensions.Localization.IStringLocalizer<Person> localizer
'Person' was the wrong class, it wasn't defined by any of the include statements. For some strange reason the only error was "OnInitialized() No method found to override."
I found this when upgrading to 6.0. I had to switch from using base classes to using partial classes!

AspNetCore RazorEngine will not use FileProviders configured in derived IOptions<RazorViewEngineOptions>

Use Case
A separated template engine that doesn't interfere in any way with normal Razor operation in an AspNetCore (2.X) web app.
Problem
Whilst trying to implement the above, I've created a whole bunch of derived wrapper classes based on RazorViewEngine, RazorViewCompilerProvider, DefaultRazorPageFactoryProvider, DefaultRazorViewEngineFileProviderAccessor and RazorViewEngineOptions in an effort that these can be registered with DI and injected whilst not having side affects in the normal Razor code path. I've succeeded except for one annoying issue, whereby I still need to configure my custom FileProvider (TemplateRepository) within the normal RazorViewEngineOptions rather than my wrapper class.
e.g. In the below code from Startup.cs, even though the file provider is specified in my custom Options object, and that is what is injected into the wrapper classes, the TemplateRepository is not called for a View request unless the second service.Configure is also included (using RazorViewEngineOptions).
services.Configure<TemplateOptions>(options =>
{
options.ViewLocationExpanders.Add(new TemplateNameExpander());
options.ViewLocationFormats.Add("{0}");
options.AreaViewLocationFormats.Add("{0}");
options.FileProviders.Clear();
options.FileProviders.Add(new TemplateRepository(new SqlConnectionFactory(configuration)));
});
services.Configure<RazorViewEngineOptions>(
options =>
{
options.FileProviders.Add(new TemplateRepository(new SqlConnectionFactory(configuration)));
});
This would suggest to me that somewhere in the RazorViewEngine dependency tree the RazorViewEngineOptions is being injected somewhere, but I cannot find it.
Full Source # GitHub
It seems that you have defined your custom RazorViewEngine but you do not tell MVC to use it.
Try to add below codes to add TemplateRazorEngine to MVC view engine.
services.Configure<TemplateOptions>(options =>
{
options.ViewLocationExpanders.Add(new TemplateNameExpander());
options.ViewLocationFormats.Add("{0}");
options.AreaViewLocationFormats.Add("{0}");
options.FileProviders.Clear();
options.FileProviders.Add(new TemplateRepository(new SqlConnectionFactory(configuration)));
});
services.Configure<MvcViewOptions>(options => {
var engine = services.BuildServiceProvider().GetRequiredService<TemplateRazorEngine>();
options.ViewEngines.Add(engine);
});
Late to the party but maybe you will use this in the future. I've also tried a lot of options and in the end I came to the conclusion that child containers would solve my particular issue. Unfortunately the AspNetCore container doesn't support them so I had to implement something quick that might not work in your case. Another option would be to use StructureMap or any other container that supports this functionality.
public class ChildServiceProvider : IServiceProvider, IDisposable
{
private readonly IServiceProvider _child;
private readonly IServiceProvider _parent;
public ChildServiceProvider(IServiceProvider parent, IServiceProvider child)
{
_parent = parent;
_child = child;
}
public ChildServiceProvider(IServiceProvider parent, IServiceCollection services)
{
_parent = parent;
_child = services.BuildServiceProvider();
}
public void Dispose()
{
(_child as IDisposable)?.Dispose();
}
public object GetService(Type serviceType)
{
return _child.GetService(serviceType) ?? _parent.GetService(serviceType);
}
}
And this is how I used it
public class Startup : IStartup
{
public IServiceProvider ChildServiceProvider { get; set; }
IServiceProvider IStartup.ConfigureServices(IServiceCollection services)
{
// Define a wrapper for the RazorViewEngine and it as a singleton
services.AddSingleton<CustomRazorEngine>(serviceProvider =>
{
// get the RazorViewEngine from the childContainer
var razorViewEngine = ChildServiceProvider.GetRequiredService<IRazorViewEngine>();
return new CustomRazorEngine(razorViewEngine);
});
return services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app)
{
ChildServiceProvider = CreateChildServiceProvider(app);
app.UseMvc();
}
IServiceProvider CreateChildServiceProvider(IApplicationBuilder parentApp)
{
// create the child container from the parentApp and register
// the custom RazorViewEngineOptions that you need for the isolated templating engine
// and whatever custom services that you need
var server = parentApp.ApplicationServices.GetRequiredService<IServer>();
var webHost = WebHost.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddOptions();
services.Configure<RazorViewEngineOptions>(opts =>
{
opts.FileProviders.Clear();
opts.FileProviders.Add(new CustomFileProvider());
});
services.AddMvc();
})
.Build();
return new ChildServiceProvider(parentApp.ApplicationServices, webHost.Services);
}
}
And the custom classes that you would need to implement
public class CustomFileProvider : IFileProvider
{
}
public class CustomRazorEngine
{
private readonly IRazorViewEngine _razorViewEngine;
public CustomRazorEngine(IRazorViewEngine razorViewEngine)
{
_razorViewEngine = razorViewEngine;
}
}
This was tested with dotnet 2.2 but haven't thoroughly tested it to be 100% that there are no performance issues or other hidden ones.
Also would be curious to know if you found another solution :)

How to load to container MassTransit EndPoint in AbstractFacility

I have a PersistenceFacility class .
This class inhereted "AbstractFacility" as "Castle.MicroKernel.Facilities".
I wonder load and install into container: IWindsorContainer.
public class PersistenceFacility: AbstractFacility
{
protected override void Init()
{
ServiceIoC.Container.Register(Component.For<IBusControl>().LifeStyle.Singleton.UsingFactoryMethod(k =>
{
var busControl = Bus.Factory.CreateUsingRabbitMq(config =>
{
config.Host(new Uri("blabla"), host =>
{
host.Username("guest");
host.Password("guest");
});
config.ReceiveEndpoint("", endpoint =>
{
endpoint.EnableMessageScope();
// Above method works but it is deprecated, instead below method should be used to get Consumer from container.
//endPoint.Consumer<YourConsumer>(container.Kernel);
});
});
return busControl;
}).LifeStyle.Singleton.Named("XXXMassTransitRMQ"));
}
// installer class
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container?.AddFacility<PersistenceFacility>();
}
}
//UOW Class
public class UnitOfWork
{
internal static IBusControl MassTransitRmqControl => ServiceIoC.Container?.Resolve<IBusControl>("XXXMassTransitRMQ");
}
First, I would consider moving to use the new container support for Windsor, as outlined in the documentation.
You can see how MassTransit registers components with Windsor by looking at the configuration.
I'm not sure an abstract facility is the way to go, given the approach taken by the links referenced above.

Trouble creating a base ViewModel for MvvmCross 5.1.0

I'm currently diving into the world of Xamarain with the MvvmCross framework. In my current project I want to make use of a MVVM base ViewModel to be able to reuse some of my code in other ViewModels.
When trying to implement this I've ran into a problem when using the MvxViewModel which supports passing parameters between navigation.
public abstract class BaseViewModel<TParameter> : MvxViewModel, IMvxViewModel<TParameter> where TParameter : class
{
protected readonly IMvxNavigationService _navigationService;
public BaseViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public new abstract Task Initialize(TParameter parameter);
}
This way I'm able to use the BaseViewModel as following.
public class ExampleViewModel : BaseViewModel<ExampleParameters>
{
private ExampleParameters _parameter;
public ExampleViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(ExampleParameters parameter)
{
return Task.Run(() => { _parameter = parameter; });
}
}
In this situation I think this is a pretty good solution. The ExampleViewModel even tells me I need to implement the Initialize Task when I've forgotten.
Still this solution is not great in every situation. When I have ViewModel that doesn't require the passing of parameters I still need to specify a parameters object and implement the Initialize method.
public class ParameterlessViewModel : BaseViewModel<object>
{
public ParameterlessViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(object parameter)
{
return Task.Run(() => { });
}
}
When removing the abstract method from the BaseViewModel I wont need to implement the Initialize method but then I won't be forced to implement it when I'm creating a ViewModel that requires the passing of parameters.
The above solution is workable but I'm curious if anyone ran into this same problem and maybe has a better solution? One which is good in both situations without having to setup two BaseViewModel classes.
Kind regards,
Jop Middelkamp
The documentation for this states: https://www.mvvmcross.com/documentation/fundamentals/navigation
If you have a BaseViewModel you might not be able to inherit MvxViewModel<TParameter> or MvxViewModel<TParameter, TResult> because you already have the BaseViewModel as base class. In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>
In case you use TResult you can just copy the source code into your viewmodel:
public override TaskCompletionSource<object> CloseCompletionSource { get; set; }
public override void ViewDestroy()
{
if (CloseCompletionSource != null && !CloseCompletionSource.Task.IsCompleted && !CloseCompletionSource.Task.IsFaulted)
CloseCompletionSource?.TrySetCanceled();
base.ViewDestroy();
}
Do we do the add the Interface IMvxViewModel in the base class or the device class, can you give a simple example
In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>

Apply aspect to all methods/actions in a class

I'm working on an ASP.NET MVC 5, I want to log all exceptions that occurs in the controller's actions.
To accomplish this I'm creating a custom aspect using PostSharp (in a dll), there I've already created the code to write the log files, now I want that the aspect can be controller-wide (do not want to apply it by hand to all methods).
The aspect's code looks like this:
using System;
using PostSharp.Aspects;
namespace Banlinea.Ceb.Domain.Aspects
{
public class LogException : OnExceptionAspect
{
public LogException()
{
ApplyToStateMachine = true;
}
public override void OnException(MethodExecutionArgs args)
{
//Code for logging the exception
args.FlowBehavior = FlowBehavior.ThrowException;
}
}
}
Now, what I want in my controller is to do something like this:
[LogException]
public class MyController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Other()
{
return View();
}
public ActionResult Another()
{
return View();
}
}
Just decorate the class, How can I do that?
you can do this byimplementing IAspectProvider
http://doc.postsharp.net/iaspectprovider
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
Type type = (Type)targetElement;
return type.GetMethods().Select(
m => return new AspectInstance(targetElement, new LogException()) );
}
You can apply PostSharp aspects across your codebase by using a feature called attribute multicasting.
For example, when you apply a method-level aspect on a class level or assembly level, then it is automatically copied to all the methods in the corresponding class or assembly. You can additionally filter the target elements by setting the attribute properties, such as AttributeTargetTypes, AttributeTargetMemberAttributes etc.
The sample code from your question should actually work as you expect.