I'm looking into upgrading this library to SignalR 2.0
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
I want it to support the 2.0 Owin pipeline with the IAppBuilder interface, instead of using RouteCollection like SignalR 1.x did.
Problem is, how can I get the routecollection from the IAppBuilder? I need it to regiser a custom IHttpHandler that handles my custom js script (Like SignalR registers their hub script)
1.x setup of my lib looks like this
public static class SignalRConfig
{
public static void Register(RouteCollection routes)
{
routes.MapHubs();
routes.MapEventProxy<Contracts.Events.Event>();
}
}
My goal for 2.0 config it something like this
public static class SignalRConfig
{
public static void Configuration(IAppBuilder app)
{
app.MapSignalR();
app.MapEventProxy<Contracts.Events.Event>();
}
}
My code that is dependent on RouteCollection looks like this
public static class RouteCollectionExtensions
{
public static void MapEventProxy<TEvent>(this RouteCollection routes)
{
Bootstrapper.Init<TEvent>();
routes.Add(new Route(
"eventAggregation/events",
new RouteValueDictionary(),
new RouteValueDictionary() {{"controller", string.Empty}},
new EventScriptRouteHandler<TEvent>()));
}
}
edit: Looks like its very complex to get Owin to serve a request, can I use helper methods in SignalR 2.0 to register a route and a handler to that route?
update:
Looks like im on the right track with this code
using Owin;
using SignalR.EventAggregatorProxy.Boostrap;
namespace SignalR.EventAggregatorProxy.Owin
{
public static class AppBuilderExtensions
{
public static void MapEventProxy<TEvent>(this IAppBuilder app)
{
Bootstrapper.Init<TEvent>();
app.Map("/eventAggregation/events", subApp => subApp.Use<EventScriptMiddleware<TEvent>>());
}
}
}
Now I just need to implement the EventScriptMiddleware
update: Last piece of the puzzle, now I just need my middleware to actually spit out the javacript, should be easy
namespace SignalR.EventAggregatorProxy.Owin
{
public class EventScriptMiddleware<TEvent> : OwinMiddleware
{
public EventScriptMiddleware(OwinMiddleware next) : base(next)
{
}
public override Task Invoke(IOwinContext context)
{
return context.Response.WriteAsync("Hello world!!");
}
}
}
Final version looks like this, app builder extension
public static class AppBuilderExtensions
{
public static void MapEventProxy<TEvent>(this IAppBuilder app)
{
Bootstrapper.Init<TEvent>();
app.Map("/eventAggregation/events", subApp => subApp.Use<EventScriptMiddleware<TEvent>>());
}
}
Invoke method in Middleware
public override Task Invoke(IOwinContext context)
{
var response = context.Response;
response.ContentType = "application/javascript";
response.StatusCode = 200;
if (ClientCached(context.Request, scriptBuildDate))
{
response.StatusCode = 304;
response.Headers["Content-Length"] = "0";
response.Body.Close();
response.Body = Stream.Null;
return Task.FromResult<Object>(null);
}
response.Headers["Last-Modified"] = scriptBuildDate.ToUniversalTime().ToString("r");
return response.WriteAsync(js);
}
Full source code here
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/tree/master/SignalR.EventAggregatorProxy/Owin
Related
I have a problem with dependecy injection in Azure Functions. I read every guide and any issue here on stackoverflow but I can't found a solution.
I'm working with Visual Studio 2019 and Azurite for testing in my local machine. I tried to make a project without and It works fine.
The project is an Azure Functions with HttpTrigger.
Here the link to my github repository
I paste here my Startup's code:
[assembly: FunctionsStartup(typeof(Startup))]
namespace ZanettiClod.SampleAzureFunctions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton<IProductService, ProductService>();
builder.Services.AddSingleton<IProductRepository<Product>, ProductRepository>();
}
}
}
And my Program's code:
namespace ZanettiClod.SampleAzureFunctions
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
host.Run();
}
}
}
My GetProducts's Code:
namespace ZanettiClod.SampleAzureFunctions
{
public class GetProducts
{
private readonly IProductService _productService;
public GetProducts(IProductService productService)
{
_productService = productService;
}
[Function("GetProducts")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get")]
HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("GetProducts");
logger.LogInformation("C# HTTP trigger function processed a request.");
var products = await _productService.GetAllProducts();
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(products);
return response;
}
}
}
And here is the error I get back:
screeshot
Thanks in advance for the help
You could make below changes to make it work. I tested for Dependency injection and it worked.
Move dependency injection from startup.cs to program.cs. That's how it works for target framework .Net 5.0.
Documentation - https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide
namespace ZanettiClod.SampleAzureFunctions
{
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(s =>
{
s.AddHttpClient();
s.AddSingleton<IProductService, ProductService>();
s.AddSingleton<IProductRepository<Product>, ProductRepository>();
})
.Build();
host.Run();
}
}
}
Change the qualifier for _productService from static to readonly in CreateProduct class. Dependency injection doesn't work on static member variables. Also remove static qualifier from your function.
public class CreateProduct
{
private readonly IProductService _productService;
public CreateProduct(IProductService productService)
{
_productService = productService;
}
[Function("CreateProduct")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("CreateProduct");
logger.LogInformation("C# HTTP trigger function processed a request.");
var product = await req.ReadFromJsonAsync<Product>();
_productService.CreateProduct(product);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(
new
{
Result = true,
Message = $"Name: {product.Name}, Price: {product.Price}"
});
return response;
}
}
}
I have set up DI for an Azure function but it will not resolve when I run the function. The code I have is:
StartUp:
[assembly: FunctionsStartup(typeof(OmegaConnector.StartUp))]
namespace OmegaConnector
{
public class StartUp : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
builder.Services.AddLogging();
builder.Services.AddVehicleSearchCosmosDataProvider();
builder.Services.AddScoped<IProcessSearchData, SearchProcessor>(); <- This one
}
}
IProcessSearchData:
public interface IProcessSearchData
{
Task<bool> ProcessData(string campaign);
}
SearchProcessor:
public class SearchProcessor : IProcessSearchData
{
public async Task<bool> ProcessData(string campaign)
{
return true;
}
}
Function:
public OmegaConnectorFunction(ILogger<OmegaConnectorFunction> logger, IProcessSearchData searchProcessor)
{
I get the error:
Executed 'CatchCampaign' (Failed, Id=daef3371-fa4d-4d1f-abad-7ad343537872)
[27/05/2020 12:17:27] Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'OmegaConnector.Interfaces.IProcessSearchData' while attempting to activate 'OmegaConnector.OmegaConnectorFunction'.
Sorry if this is too simple but I just can't see what I have wrong here. I think I have this set up correctly but I obviously don't. Can anyone see what I need to do?
See here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library
The Functions 3.x packages are built with .NET Core 3.1 in mind.
Try keeping these versions in sync so there are no dependency compatibility problems.
From what I understood of the documentation provided by Microsoft the issue may be that the service needs to be injected into the class that contains the function.
I'm unsure if this is what you've done from the code examples you've provided. An example of this is:
public class OmegaConnectorFunction
{
private readonly ILogger _logger;
private readonly IProcessSearchData _searchProcessor;
public OmegaConnectorFunction(ILogger<OmegaConnectorFunction> logger, IProcessSearchData searchProcessor)
{
_logger = logger;
_searchProcessor = searchProcessor;
}
[FunctionName("OmegaConnectorFunction")]
public async Task<IActionResult> Run([HttpTrigger] HttpRequest request)
{
var campaign = await request.Content.ReadAsAsync<string>();
_searchProcessor.ProcessData(campaign);
return new OkResult();
}
}
public class OmegaConnectorFunction {
private readonly IProcessSearchData _searchProcessor;
public OmegaConnectorFunction(IProcessSearchData searchProcessor)
{
_searchProcessor = searchProcessor;
}
[FunctionName("OmegaConnectorFunction")]
public async Task<IActionResult> Run([HttpTrigger] HttpRequest request, ILogger log) // ILogger is automatically imported
{
var campaign = await request.Content.ReadAsAsync<string>();
_searchProcessor.ProcessData(campaign);
return new OkResult();
}
}
I'm working on a program where I receive data from SignalR, perform processing, and then send a SignalR message back to the client once the processing has finished. I've found a couple of resources for how to do this, but I can't quite figure out how to implement it in my project.
Here's what my code looks like:
Bootstrapping
public static void Main(string[] args)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
List<ISystem> systems = new List<ISystem>
{
new FirstProcessingSystem(),
new SecondProcessingSystem(),
};
Processor processor = new Processor(
cancellationToken: cancellationTokenSource.Token,
systems: systems);
processor.Start();
CreateHostBuilder(args).Build().Run();
cancellationTokenSource.Cancel();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<TestHub>("/testHub");
});
}
}
TestHub.cs
public class TestHub : Hub
{
public async Task DoStuff(Work work)
{
FirstProcessingSystem.ItemsToProcess.Add(work);
}
}
Work.cs
public class Work
{
public readonly string ConnectionId;
public readonly string Data;
public Work(string connectionId, string data)
{
ConnectionId = connectionId;
Data = data;
}
}
Processor.cs
public class Processor
{
readonly CancellationToken CancellationToken;
readonly List<ISystem> Systems;
public Processor(
CancellationToken cancellationToken,
List<ISystem> systems)
{
CancellationToken = cancellationToken;
Systems = systems;
}
public void Start()
{
Task.Run(() =>
{
while (!CancellationToken.IsCancellationRequested)
{
foreach (var s in Systems)
s.Process();
}
});
}
}
Systems
public interface ISystem
{
void Process();
}
public class FirstProcessingSystem : ISystem
{
public static ConcurrentBag<Work> ItemsToProcess = new ConcurrentBag<Work>();
public void Process()
{
while (!ItemsToProcess.IsEmpty)
{
Work work;
if (ItemsToProcess.TryTake(out work))
{
// Do things...
SecondProcessingSystem.ItemsToProcess.Add(work);
}
}
}
}
public class SecondProcessingSystem : ISystem
{
public static ConcurrentBag<Work> ItemsToProcess = new ConcurrentBag<Work>();
public void Process()
{
while (!ItemsToProcess.IsEmpty)
{
Work work;
if (ItemsToProcess.TryTake(out work))
{
// Do more things...
// Hub.Send(work.ConnectionId, "Finished");
}
}
}
}
I know that I can perform the processing in the Hub and then send back the "Finished" call, but I'd like to decouple my processing from my inbound messaging that way I can add more ISystems when needed.
Can someone please with this? (Also, if someone has a better way to structure my program I'd also appreciate the feedback)
aspnet has a very powerful dependency injection system, why don't you use it? By creating your worker services without dependency injection, you'll have a hard time using anything provided by aspnet.
Since your "processing systems" seem to be long running services, you'd typically have them implement IHostedService, then create a generic service starter (taken from here):
public class BackgroundServiceStarter<T> : IHostedService where T : IHostedService
{
readonly T _backgroundService;
public BackgroundServiceStarter(T backgroundService)
{
_backgroundService = backgroundService;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return _backgroundService.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _backgroundService.StopAsync(cancellationToken);
}
}
then register them to the DI container in ConfigureServices:
// make the classes injectable
services.AddSingleton<FirstProcessingSystem>();
services.AddSingleton<SecondProcessingSystem>();
// start them up
services.AddHostedService<BackgroundServiceStarter<FirstProcessingSystem>>();
services.AddHostedService<BackgroundServiceStarter<SecondProcessingSystem>>();
Now that you got all that set up correctly, you can simply inject a reference to your signalR hub using IHubContext<TestHub> context in the constructor parameters of whatever class that needs it (as described in some of the links you posted).
I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
I've just started playing with IoC containers and therefore chosed Ninject.
After several hours of sweat and tears I still cant figure out how to setup my MVC3 application with Ninject.
So far I have:
Global.asax.cs
public class MvcApplication : Ninject.Web.Mvc.NinjectHttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
DependencyResolver.SetResolver(new MyDependencyResolver(CreateKernel()));
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected override IKernel CreateKernel()
{
var modules = new [] { new ServiceModule() };
return new StandardKernel(modules);
}
}
ServiceModule.cs
internal class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<IGreetingService>().To<GreetingService>();
}
}
MyDependencyResolver.cs
public class MyDependencyResolver : IDependencyResolver
{
private IKernel kernel;
public MyDependencyResolver(IKernel kernel)
{
this.kernel = kernel;
}
public object GetService(System.Type serviceType)
{
return kernel.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(System.Type serviceType)
{
return kernel.GetAll(serviceType);
}
}
GreetingService.cs
public interface IGreetingService
{
string Hello();
}
public class GreetingService : IGreetingService
{
public string Hello()
{
return "Hello from GreetingService";
}
}
TestController.cs
public class TestController : Controller
{
private readonly IGreetingService service;
public TestController(IGreetingService service)
{
this.service = service;
}
public ActionResult Index()
{
return View("Index", service.Hello());
}
}
Each time I try to load the Index view it either just throws a overflow exception or a HTTP 404 error - If I remove all the Ninject code it works perfectly, whats wrong?
You are mixing an own dependency resolver with the MVC extension. I'd suggest either going with your own dependency resolver or with using the MVC extension but not both. When using the MVC extension you have to use OnApplicationStarted instead of Application_Start.
See http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/ and have a look at the SampleApplication that comes with the source code of the MVC extension https://github.com/ninject/ninject.web.mvc.
Also the fix is not used anymore when you use the current version for the build server: http://teamcity.codebetter.com
UPDATE: The Ninject.MVC3 package continues to be updated and works OOTB against MVC4 RTM (and RC). See this page in the wiki for details.