How to pass IHttpContextAccessor to DotLiquid's custom tag - asp.net-core-2.0

My ASP.NET Core application is using dotliquid. I am creating my own custom dotliquid tag and I want to inject IHttpContextAccessor in my custom tag. Based on my research i have to create ITagFactory to create tag and inject IHttpContextAccessor.
my custom factory
public class LiquidTagFactory : DotLiquid.ITagFactory
{
private readonly Type _tagType;
private readonly string _tagName;
private readonly IHttpContextAccessor _contextAccessor;
public string TagName { get { return _tagName; } }
public LiquidTagFactory(Type tagType, string tagName, IHttpContextAccessor contextAccessor)
{
_tagType = tagType;
_tagName = tagName;
_contextAccessor = contextAccessor;
}
public Tag Create()
{
return (Tag)Activator.CreateInstance(_tagType, _contextAccessor);
}
}
my custom tag
public class TextBox : DotLiquid.Tag
{
private string _html = null;
private readonly IHttpContextAccessor _contextAccessor;
public TextBox(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public override void Initialize(string tagName, string markup, List<string> tokens)
{
// here i want to store some values into HttpContext.Items
_contextAccessor.HttpContext.Items.Add("key","somedata");
base.Initialize(tagName, markup, tokens);
}
public override void Render(Context context, TextWriter result)
{
_html = CreateHtml();
result.Write(_html);
base.Render(context, result);
}
}
Then i would register this factory as
Template.RegisterTagFactory(new LiquidTagFactory(typeof(TexBox), "textbox", _httpContextAccessor));
However, i am using ASP.NET Core. I am not sure how do i register custom tag factory using .NET Core's dependency injection?
looking at dotliquid's template code it stores the ITagFactory in private static field
private static Dictionary<string, Tuple<ITagFactory, Type>> Tags { get; set;}
In my case, every request will have its own IHttpContextAccessor, and i don't want to create factory for each request

I think i found it. As per SO we can register IHttpContextAccessor singleton.
As of .NET Core 2.1 there is an extension method that has been added to correctly register an IHttpContextAccessor as a singleton. Simply add as follows in your ConfigureServices() method:
services.AddHttpContextAccessor();
This is equivalent to:
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
and then register the factory in Configure() method in startup. Make sure to pass IHttpContextAccessor as parameter
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<DomainOptions> domainOps, IHttpContextAccessor httpContextAccessor)
{
Template.RegisterTagFactory(new LiquidTagFactory(typeof(TextBox), "textbox", httpContextAccessor));
}

Related

How to use DI in this example?

I followed this example https://learn.microsoft.com/pt-br/azure/app-service/webjobs-sdk-get-started and it is working fine. What I want to do is to make the connection strings (strongly typed) available in all methods within Functions class. My Connection Strings object:
namespace MyApp.Domain
{
public class Secrets
{
public class ConnectionStrings
{
public string SqlServer {get; set;}
public string Storage {get; set;}
public string SendGrid {get; set;}
public string AzureWebJobsDashboard { get; set; }
public string AzureWebJobsStorage {get; set;}
}
}
}
In web project I use (and it works perfectly):
services.Configure<Secrets.ConnectionStrings>(Configuration.GetSection("CUSTOMCONNSTR_ConnectionStrings"));
and in the classes' constructors I use:
public class EmailController: ControllerBase
{
private readonly MyEmail _myEmail;
public EmailController(MyEmail MyEmail)
{
_myEmail = MyEmail;
}
[HttpGet]
public async Task<ActionResult<string>> SendEmail()
{
try
{
...
return await _myEmail.SendMailMI3D(myMsg);
}
catch (System.Exception ex)
{
return ex.Message + " - " + ex.StackTrace;
}
}
[HttpGet("sendgrid")]
public string GetSendGrid(long id)
{
return _myEmail.SendGridConnStr();
}
}
But this way doesn't work on webjobs (console apps).
I tried to insert a simple Console.WriteLine in Functions' constructor but it doesn't work as well. So I think this is the problem: Functions' constructor is not being called. So when I insert a message in my queue I receive this error message related to DI Connection String:
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: Functions.ProcessQueueMessage ---> System.NullReferenceException: Object reference not set to an instance of an object.
Can anybody please help me? Thanks a lot.
public Functions(IOptions<Secrets.ConnectionStrings> ConnectionStrings)
{
_connectionStrings = ConnectionStrings;
Console.WriteLine("Simple line");
Console.WriteLine($"Functions constructor: ${_connectionStrings.Value.SendGrid}");
}
Microsoft.Azure.WebJobs.Host.FunctionInvocationException: Exception while executing function: Functions.ProcessQueueMessage ---> System.NullReferenceException: Object reference not set to an instance of an object.
Dependency Injection is available in WebJobs but you do need to take the extra step to create an IJobActivator to define the injection.
namespace NetCoreWebJob.WebJob
{
public class JobActivator : IJobActivator
{
private readonly IServiceProvider services;
public JobActivator(IServiceProvider services)
{
this.services = services;
}
public T CreateInstance<T>()
{
return services.GetService<T>();
}
}
}
Inside Main()
var config = new JobHostConfiguration();
config.JobActivator = new JobActivator(services.BuildServiceProvider());
That should allow the runtime to utilize the parameterized constructor.

accessing Azure Cloud Storage with the new Configuration in ASP.NET Core 2.0

Is there a way to configure my Azure Cloud storage in ConfigureServices of the Startup class, the same way I configure my DatabaseContext?
var connection = Configuration.GetConnectionString("myDatabaseConnectionString");
services.AddDbContext<DatabaseContext>
(options => options.UseSqlServer(connection));
I know I could access the connection string from within a controller by adding the IConfiguration instance to the service collection as a singleton object in ConfigureServices and then inject IConfiguration in the controller.
But how do I access this in the following class or is this a bad practice and there is a better way (e.g. in the services.Add ...) ?
public class ClassifiedsToProcess
{
public static void AddMessage(string message)
{
var storageConnectionString = Configuration.Get("AppSettings:ConnectionString");
storageAccount = CloudStorageAccount.Parse(storageConnectionString);
...
}
}
Thanks !
Here is how I solved my problem using IOptions as suggested by Martin Brandl
Controller code
private string makeJson;
private MyAzureQueue MyAzureQueue;
public AdminController(IOptions<AzureOptions> optionsAccessor)
{
MyAzureQueue = new MyAzureQueue(optionsAccessor);
}
public IActionResult Index()
{
MyAzureQueue.AddMessage("my message");
return View();
}
MyAzureQueue class
public interface IMessageRepository
{
void AddMessage(string message);
}
public class MyAzureQueue: IMessageRepository
{
private readonly CloudQueue _queue;
public ClassifiedsToProcess(IOptions<AzureOptions> optionsAccessor)
{
CloudStorageAccount storageAccount = AzureStorageHelper.getStorageAccount(optionsAccessor.Value.StorageConnectionString);
_queue = AzureStorageHelper.GetQueue(storageAccount, "mystorageaccount");
}
public void AddMessage(string message)
{
CloudQueueMessage cloudQueueMessage = new CloudQueueMessage(message);
_queue.AddMessageAsync(cloudQueueMessage);
}
}
Azure Options class
public class AzureOptions
{
public AzureOptions()
{
}
public string StorageConnectionString { get; set; }
}
In the Startup.cs ConfigureServices method I added the following two lines
services.AddOptions();
services.Configure<AzureOptions>(Configuration.GetSection("Azure"));
And this is what my appsettings.json looks like
{
"Azure": {
"StorageConnectionString": "DefaultEndpointsProtocol=https;AccountName=mystorageaccount;AccountKey=XXXXXXXXXXXXXXXXXXXXX;EndpointSuffix=core.windows.net"
}
}
You should try to avoid using IConfiguration directly and use the Option pattern in
ASP.NET Core instead.
In your example, I would e. g. implement an IMessageRepository which uses the storage account to persist the messages.
Your ClassifiedsToProcess class would have a constructor dependency to it and your AddMessage method would used it (it shouldn't be static since you want to access the repository).

NServiceBus Configuration with Custom Container

I am trying to re-use the service registrations in an assembly that I use through a few services in my solution. I follow the example listed from the NServiceBus website to implement the solution. When following that, unless I add the IWantCustomInitialization interface, my Init method (and IoC container implementation) appears not to function. When I have that interface implemented, I get exceptions (listed in SO questions here and here). I can't seem to get it to work that there are no exceptions AND the dependencies in my MessageHandler are being populated properly. Here is my current EndpointConfig implementation.
[EndpointSLA("00:00:30")]
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, UsingTransport<Msmq>, INeedInitialization {
public void Init() {
Configure.With().ObjectBuilderAdapter();
}
}
public class ObjectBuilderAdapter : IContainer {
readonly IDependencyInjector injector;
public ObjectBuilderAdapter(IDependencyInjectionBuilder dependencyInjectionBuilder) {
injector = dependencyInjectionBuilder.Create(); //This method does all the common service registrations that I am trying to re-use
//injector.RegisterType<ExtractIncomingPrincipal, PrincipalExtractor>();
}
public void Dispose() {
injector.Dispose();
}
public object Build(Type typeToBuild) {
return injector.Resolve(typeToBuild);
}
public IContainer BuildChildContainer() {
return new ObjectBuilderAdapter(new DependencyInjectorBuilder());
}
public IEnumerable<object> BuildAll(Type typeToBuild) {
return injector.ResolveAll(typeToBuild);
}
public void Configure(Type component, DependencyLifecycle dependencyLifecycle) {
injector.RegisterType(component);
}
public void Configure<T>(Func<T> component, DependencyLifecycle dependencyLifecycle) {
injector.RegisterType(component);
}
public void ConfigureProperty(Type component, string property, object value) {
if (injector is AutofacDependencyInjector) {
((AutofacDependencyInjector)injector).ConfigureProperty(component, property, value);
} else {
Debug.WriteLine("Configuring {0} for property {1} but we don't handle this scenario.", component.Name, property);
}
}
public void RegisterSingleton(Type lookupType, object instance) {
injector.RegisterInstance(lookupType, instance);
}
public bool HasComponent(Type componentType) {
return injector.IsRegistered(componentType);
}
public void Release(object instance) { }
}
public static class Extensions {
public static Configure ObjectBuilderAdapter(this Configure config) {
ConfigureCommon.With(config, new ObjectBuilderAdapter(new DependencyInjectorBuilder()));
return config;
}
}
Note: When I use the INeedInitialization interface, I get the ComponentNotRegisteredException when it's looking for IStartableBus.
When you are trying to swap the built in container, then you need to implement IWantCustomInitialization in the same class that implements IConfigureThisEndpoint.
You can use your own container and register all your types in there and tell NSB to use that container.
For example:
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
{
public void Init()
{
var container = new ContainerBuilder().Build();
Configure.With()
.AutofacBuilder(container);
}
}

Accessing the calling Service from ServiceRunner?

I want to access the calling Service from inside the ServiceRunner OnBeforeRequest()method in order to get to an object in the calling service class. In MVC, I can create a class BaseController that overrides OnActionExecuting() and I can get to Data easily. However, using ServiceRunner, since it's not derived from Service, I don't see a way to get to the Service object.
Sample service:
public class ProductsService : Service
{
private MyData _data = new MyData();
public MyData Data
{
get { return _data; }
}
public object Get(GetProduct request)
{
// ...
return product;
}
}
In my custom ServiceRunner, how do I retrieve the ProductsService object from OnBeforeRequest() so I can get to Data?
public class MyServiceRunner<T> : ServiceRunner<T>
{
public override void OnBeforeExecute(IRequestContext requestContext, T request)
{
// var productService = ?
base.OnBeforeExecute(requestContext, request);
}
}
After much digging, it looks like this cannot be done. The Service action is available in the ServiceRunner as an unnamed lamdba delegate. There is no reference to the Service.
I have instead found a workaround. I first registered MyData in AppHost.Configure() using
container.RegisterAutoWired<MyData>();
I moved the MyData declaration to a filter attribute like this:
public class UseMyDataAttribute : RequestFilterAttribute
{
public MyData Data { get; set; } // injected by Funq IoC.
public override void Execute(IHttpRequest req, IHttpResponse res, object responseDto)
{
Data.SessionID = req.GetSessionId();
}
}
This way I can apply [UseMyData] to the ProductsService class and be able to set the Session ID to Data.

Custom attributes to ServiceStack Methods

I am trying to create a Custom Attribute for a ServiceStack Service with which I can control each method in the service Class.
This is the attribute class that I am implementing.
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class , Inherited = false, AllowMultiple = false)]
public class MyRequestFilterAttribute:RequestFilterAttribute
{
public string Provider { get; set; }
public MyRequestFilterAttribute(ApplyTo applyTo): base(applyTo)
{
this.Priority = (int) RequestFilterPriority.Authenticate;
}
public MyRequestFilterAttribute():this(ApplyTo.All)
{
}
public MyRequestFilterAttribute(ApplyTo applyTo, string provider): this(applyTo)
{
this.Provider = provider;
}
public MyRequestFilterAttribute(string provider): this(ApplyTo.All)
{
this.Provider = provider;
}
public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
{}
}
This is the Service Class
[MyRequestFilter(ApplyTo.All)]
public class TodoService : RestServiceBase<Todo>
{
public TodoRepository Repository { get; set; }
public override object OnGet(Todo request)
{
if (request.Id == default(long))
return Repository.GetAll();
return Repository.GetById(request.Id);
}
public override object OnPost(Todo todo)
{
return Repository.Store(todo);
}
public override object OnPut(Todo todo)
{
return Repository.Store(todo);
}
[MyRequestFilter("Admin")]
public override object OnDelete(Todo request)
{
Repository.DeleteById(request.Id);
return null;
}
public object GetDetailsofALL()
{
return null;
}
}
I am able to get control on the Methods when I place the attribute on the Class level.
i.e
[MyRequestFilter(ApplyTo.All)]
public class TodoService : RestServiceBase<Todo>{}
What I require is to place an attribute on the method level as well and do some authentication such that only an admin has the right to perform this method of the service.
But when I place this attribute in the Delete method it is not working and the custom filter attribute is not getting hit. Only the class level attribute call works.
[MyRequestFilter("Admin")]
public override object OnDelete(Todo request){}
Is it possible to set the permission/filter attribute from the method level? If yes, how can I implement this?
No it's not possible to add it on the method. You can add it on the service class or the Request DTO and use the ApplyTo method filter to tell servicestack which methods it should apply to, e.g:
[MyRequestFilter(ApplyTo.All)]
[MyRequestFilter(ApplyTo.Delete, "Admin")]
public class TodoService : RestServiceBase<Todo> { ... }
You can also refer to the implementation of the [RequiredRole] and [RequestPermission] attributes for examples of Request Filter attributes that support this.

Resources