I am using Http Trigger Azure Function, after receiving request message I am uploading that message into Azure blob storage.
Can I inject Binder in the class constructor?
Below is my Http Trigger Azure Function
namespace Notifications.Receiver.Publishers
{
/// <summary>
/// InvoiceMessageWebHook - Http Trigger Azure function
/// </summary>
public class InvoiceMessageWebHook
{
#region private fields
private readonly ILogger<InvoiceMessageWebHook> logger;
private readonly IEventValidator validator;
private readonly IEventHandler<InvoiceResultDto, InvoiceMessageEvent> handler;
#endregion
#region ctors
public InvoiceMessageWebHook(ILogger<InvoiceMessageWebHook> logger,
IEventValidator validator,
IEventHandler<InvoiceResultDto, InvoiceMessageEvent> handler)
{
this.logger = logger;
this.validator = validator;
this.handler = handler;
}
#endregion
#region function
/// <summary>
/// InvoiceMessageWebHook - Http Trigger Azure function
/// </summary>
/// <param name="req"></param>
/// <param name="binder"></param>
/// <returns></returns>
[FunctionName(nameof(InvoiceMessageWebHook))]
public async Task<IActionResult> RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
Binder binder)
{
try
{
IActionResult actionResult = null;
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
logger.LogInformation($"Invoice message request received on {nameof(InvoiceMessageWebHook)} request body:{requestBody.AsJson()}");
var #event = requestBody.AsPoco<InvoiceMessageEvent>();
#event.ReceivedDate = $"{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff}";
if (validator.Validate(#event, new InvoiceMessageEventValidator(), logger, ref actionResult))
{
var response = await handler.HandleAsync(#event, binder);
actionResult = new OkObjectResult(response);
}
return actionResult;
}
catch (Exception ex)
{
logger.LogError($"Exception while processing {nameof(InvoiceMessageWebHook)}," +
$" Exception Message:{ex.Message}, StackTrace:{ex.StackTrace}");
throw;
}
}
#endregion
}
}
Below is my handler:
namespace Notifications.Receiver.Events.Handlers
{
/// <summary>
/// Handler
/// </summary>
public class InvoiceMessageEventHandler : IEventHandler<InvoiceResultDto, InvoiceMessageEvent>
{
#region private fields
public const string ServiceBusTopicNotificationMonitoring = "ServiceBusTopicNotificationMonitoring";
public const string ServiceBusConnectionString = "ServiceBusConnectionString";
private readonly ILogger<InvoiceMessageEventHandler> logger;
private readonly IMessageBusFactory messageBusFactory;
#endregion
#region ctors
public InvoiceMessageEventHandler(ILogger<InvoiceMessageEventHandler> logger,
IMessageBusFactory messageBusFactory)
{
this.logger = logger;
this.messageBusFactory = messageBusFactory;
}
#endregion
#region Handler
/// <summary>
/// Upload invoice message into Blob storage
/// </summary>
/// <param name="event"></param>
/// <param name="binder"></param>
/// <param name="logger"></param>
/// <returns></returns>
public async Task<InvoiceResultDto> HandleAsync(InvoiceMessageEvent #event, Binder binder)
{
#event.RequestID = Guid.NewGuid().ToString();
#event.MonitoringType = MonitoringType.EventReceiver.ToString();
var blockBlob = await GetCloudBlockBlob(#event, binder);
logger.LogInformation($"Start uploading invoice message into blob storage." +
$" requestID:{#event.RequestID}");
await blockBlob.UploadTextAsync(#event.AsJson());
logger.LogInformation($"Successfully uploaded invoice message into blob storage." +
$" requestID:{#event.RequestID}");
//Publishing message into Service Bus Topic
await PublishMessageIntoNotificationMonitoringAsync(#event, logger);
return new InvoiceResultDto
{
RequestId = #event.RequestID,
OverallStatus = ResultType.Success.ToString().ToUpper()
};
}
#endregion
#region private methods
/// <summary>
/// GetCloudBlockBlob
/// </summary>
/// <param name="binder"></param>
/// <param name="fileName"></param>
/// <returns></returns>
private async Task<CloudBlockBlob> GetCloudBlockBlob(InvoiceMessageEvent #event, Binder binder)
{
var container = $"{BlobStorage.ContainerName}";
var attributes = new Attribute[]
{
new BlobAttribute($"{container}", FileAccess.ReadWrite),
new StorageAccountAttribute(StorageConnectionString.Name)
};
var outputContainer = await binder.BindAsync<CloudBlobContainer>(attributes);
await outputContainer.CreateIfNotExistsAsync();
var blockBlob = outputContainer.GetBlockBlobReference($"messages/{#event.RequestID}.json");
return blockBlob;
}
/// <summary>
/// Publish message into NotificationMonitoring Service Bus Topic
/// </summary>
/// <param name="event"></param>
/// <returns></returns>
private async Task PublishMessageIntoNotificationMonitoringAsync(InvoiceMessageEvent #event, ILogger logger)
{
var serviceBusTopicNotificationMonitoring = Environment.GetEnvironmentVariable(ServiceBusTopicNotificationMonitoring);
Ensure.ConditionIsMet(serviceBusTopicNotificationMonitoring.IsNotNullOrEmpty(),
() => throw new ArgumentNullException($"{nameof(ServiceBusTopicNotificationMonitoring)} not configured"));
logger.LogInformation($"Start publishing the invoice message with requestID: {#event.RequestID} " +
$"into service bus topic: {serviceBusTopicNotificationMonitoring}");
var serviceBusClient = messageBusFactory.GetClient(serviceBusTopicNotificationMonitoring);
await serviceBusClient.PublishMessageAsync<InvoiceMessageEvent>(#event, #event.RequestID, nameof(#event.MonitoringType), #event.MonitoringType);
logger.LogInformation($"Successfully published the invoice message with requestID: {#event.RequestID} " +
$"into service bus topic: {serviceBusTopicNotificationMonitoring}");
}
#endregion
}
}
Related
I have trying to write integration tests for my SignalR chat application.
But the connection.On() callbacks never gets fired.
While debugging I have made sure InvokeAsync works perfectly fine but not the callbacks
Also I have tested my hub with Angular client and it works fine.
Test.cs
public sealed class ChatHubTest : ControllerTests
{
private IHost? host_;
[Test]
public async Task ReplyWithTheSameMessageWhenInvokeSendMethod()
{
//var message = "Integration Testing in Microsoft AspNetCore SignalR";
string echo = "";
var hostBuilder = new HostBuilder()
.ConfigureWebHost(webHost => _ = webHost
.UseTestServer()
.UseStartup<Startup>()
.UseEnvironment("IntegrationTest"));
host_?.Dispose();
host_ = await hostBuilder.StartAsync();
TestServer server = host_.GetTestServer();
var connection = new HubConnectionBuilder()
.WithUrl(
"http://localhost:5000/hub/chat?username=21671", options =>
{
options.AccessTokenProvider = () => Task.FromResult("hello");
options.HttpMessageHandlerFactory = _ => server.CreateHandler();
})
.Build();
using IDisposable handler = connection.On<(int, string)>(WebSocketActions.GroupAction, msg =>
{
(_, echo) = msg;
_ = echo.Should().Be("Typing");
});
await connection.StartAsync();
await connection.InvokeAsync("JoinGroup", "group1");
await connection.InvokeAsync("SendToGroup", "group1", 123, "Typing");
_ = echo.Should().Be("Typing");
}
}
Hub.cs
public sealed class ChatHub : Hub
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Join a socket group </summary>
///
/// <param name="group"> Identifier for the group. </param>
///
/// <returns> . </returns>
//[Authorize(Policy = "CustomHubAuthorizatioPolicy")]
public async Task JoinGroup(string group)
{
await Groups.AddToGroupAsync(Context.ConnectionId, group);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Send action to group </summary>
///
/// <param name="group"> Identifier for the group. </param>
/// <param name="userId"> Identifier for the user. </param>
/// <param name="action"> Action to send </param>
///
/// <returns> . </returns>
//[Authorize(Policy = "CustomHubAuthorizatioPolicy")]
public async Task SendToGroup(string group, int userId, string action)
{
await Clients.Group(group).SendAsync(WebSocketActions.GroupAction, userId, action);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary> Leave group </summary>
///
/// <param name="group"> Identifier for the group. </param>
///
/// <returns> . </returns>
//[Authorize(Policy = "CustomHubAuthorizatioPolicy")]
public async Task LeaveGroup(string group)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, group);
}
}
The hub works with angular client.
Thanks In Advance.
I'm thinking I must be missing something obvious, but im trying to create a new DesignAutomationClient object like follows:
private void runDAButton_Click(object sender, EventArgs e)
{
createWorkItem();
}
private async Task createWorkItem()
{
var forgeConfig = new Autodesk.Forge.Core.ForgeConfiguration();
forgeConfig.ClientId = clientID;
forgeConfig.ClientSecret = clientSecret;
var apiInstance = new DesignAutomationClient();
// Code to create work item will go here
}
but when I do, the following error appears in my Visual Studio Debug/Immediate window after trying to execute the var apiInstance = new DesignAutomationClient(); line:
Exception thrown: 'System.TypeLoadException' in mscorlib.dll
Am i missing something obvious? The design automation client was downloaded using NuGet so i should have all the required depencies, but searches of forums for this kind of error all say it means I'm either missing a DLL file, or the type I'm looking for doesn't exist within a DLL, neither of which I believe are true.
This code is in a simple windows form application written in C#
There are no web servers or ASP.NET involved.
The user clicks a button on the form which runs the runDAButton_Click function(which in turn runs the createWorkItem() function). That function should create an instance of the API, and then use it to create my work item.
Can anyone help?
We need more information to troubleshoot, is it a ASP .NET core? how you are handling DI
but if your app is .NET core console app from the code as it appears.
The right way to do is.
dotnet new console
dotnet add package Autodesk.Forge.DesignAutomation --version 3.0.3
Code:
namespace daconsole
{
using Autodesk.Forge.Core;
using Autodesk.Forge.DesignAutomation;
using Autodesk.Forge.DesignAutomation.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Defines the <see cref="ConsoleHost" />.
/// </summary>
class ConsoleHost : IHostedService
{
/// <summary>
/// The StartAsync.
/// </summary>
/// <param name="cancellationToken">The cancellationToken<see cref="CancellationToken"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// The StopAsync.
/// </summary>
/// <param name="cancellationToken">The cancellationToken<see cref="CancellationToken"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
/// <summary>
/// Defines the <see cref="App" />.
/// </summary>
internal class App
{
/// <summary>
/// Defines the api.
/// </summary>
public DesignAutomationClient api;
/// <summary>
/// Defines the config.
/// </summary>
public ForgeConfiguration config;
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
/// <param name="api">The api<see cref="DesignAutomationClient"/>.</param>
/// <param name="config">The config<see cref="IOptions{ForgeConfiguration}"/>.</param>
public App(DesignAutomationClient api, IOptions<ForgeConfiguration> config)
{
this.api = api;
this.config = config.Value;
}
/// <summary>
/// The CreateWorkItem.
/// </summary>
/// <returns>The <see cref="Task"/>.</returns>
private async Task CreateWorkItem()
{
//step1:
var forgeEnginesApi = api.EnginesApi;
ApiResponse<Page<string>> engines = await forgeEnginesApi.GetEnginesAsync();
if (engines.HttpResponse.IsSuccessStatusCode)
{
Console.WriteLine(JsonConvert.SerializeObject(engines.Content, Formatting.Indented));
}
//step2:
Console.WriteLine("\nActiviy Start");
var activitiesApi = api.ActivitiesApi;
ApiResponse<Page<string>> activitiesResp = await activitiesApi.GetActivitiesAsync();
List<string> listOfActivities = new List<string>();
string activityName = null;
if (activitiesResp.HttpResponse.IsSuccessStatusCode)
{
var page = activitiesResp.Content.PaginationToken;
activitiesResp.Content.Data.ForEach(e => listOfActivities.Add(e));
while (page != null)
{
activitiesResp = await activitiesApi.GetActivitiesAsync(page);
page = activitiesResp.Content.PaginationToken;
activitiesResp.Content.Data.ForEach(e => listOfActivities.Add(e));
}
var activities = listOfActivities.Where(a => a.Contains("PlotToPDF")).Select(a => a);
if (activities.Count() > 0)
{
activityName = activities.FirstOrDefault();
}
}
//step3:
Console.WriteLine("\nWorkItem Start...");
var workItemsApi = api.WorkItemsApi;
ApiResponse<WorkItemStatus> workItemStatus = await workItemsApi.CreateWorkItemAsync(new Autodesk.Forge.DesignAutomation.Model.WorkItem()
{
ActivityId = activityName,
Arguments = new Dictionary<string, IArgument>() {
{
"HostDwg",
new XrefTreeArgument() {
Url = "http://download.autodesk.com/us/samplefiles/acad/blocks_and_tables_-_metric.dwg",
Verb = Verb.Get
}
}, {
"Result",
new XrefTreeArgument() {
Verb = Verb.Put, Url = "azure blob storage url",
Headers = new Dictionary<string,string>()
{
{ "Content-Type","application/octet-stream" },
{ "x-ms-blob-type","BlockBlob" }
}
}
}
}
});
Console.Write("\tPolling status");
while (!workItemStatus.Content.Status.IsDone())
{
await Task.Delay(TimeSpan.FromSeconds(2));
workItemStatus = await workItemsApi.GetWorkitemStatusAsync(workItemStatus.Content.Id);
Console.Write(".");
}
Console.WriteLine(JsonConvert.SerializeObject(workItemStatus.Content, Formatting.Indented));
}
/// <summary>
/// The RunAsync.
/// </summary>
/// <returns>The <see cref="Task"/>.</returns>
public async Task RunAsync()
{
await CreateWorkItem();
}
}
/// <summary>
/// Defines the <see cref="Program" />.
/// </summary>
internal class Program
{
/// <summary>
/// The Main.
/// </summary>
/// <param name="args">The args<see cref="string[]"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(builder =>
{
builder.AddEnvironmentVariables();
builder.AddForgeAlternativeEnvironmentVariables();
}).ConfigureServices((hostContext, services) =>
{ // add our no-op host (required by the HostBuilder)
services.AddHostedService<ConsoleHost>();
// our own app where all the real stuff happens
services.AddSingleton<App>();
// add and configure DESIGN AUTOMATION
services.AddDesignAutomation(hostContext.Configuration);
services.AddOptions();
})
.UseConsoleLifetime()
.Build();
using (host)
{
await host.StartAsync();
// Get a reference to our App and run it
var app = host.Services.GetRequiredService<App>();
await app.RunAsync();
await host.StopAsync();
}
}
}
}
add Forge Env to your launchSettings.json
{
"profiles": {
"daconsole": {
"commandName": "Project",
"environmentVariables": {
"FORGE_CLIENT_SECRET": "",
"FORGE_CLIENT_ID": ""
}
}
}
}
To run:
dotnet run --launch-profile daconsole
Ciao,
I'm developing a series of microservices with aspnet core. I want to return a custom http header on 500 responses.
I tried to create a custom ASP.NET Core Middleware that update context.Response.Headers property but it works only when the response is 200.
This is my custom middleware:
namespace Organizzazione.Progetto
{
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public ExtractPrincipalMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.Headers.Add("X-Correlation-Id", Guid.NewGuid());
await _next.Invoke(context);
return;
}
}
}
This is my configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<MyCustomMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API");
});
}
How can I return my custom header on a 500 response caused by unhandled exception (or possibly on all the responses)?
Thank you a lot
You have to subscribe on httpContext.Response.OnStarting
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Response.OnStarting((Func<Task>)(() =>
{
httpContext.Response.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
return Task.CompletedTask;
}));
try
{
await this._next(httpContext);
}
catch (Exception)
{
//add additional exception handling logic here
//...
httpContext.Response.StatusCode = 500;
}
}
}
And register it your Starup
app.UseMiddleware(typeof(CorrelationIdMiddleware));
You can use a global filter :
public class CorrelationIdFilter : IActionFilter
{
/// <summary>
/// Called after the action executes, before the action result.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" />.</param>
public void OnActionExecuted(ActionExecutedContext context)
{
}
/// <summary>
/// Called before the action executes, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" />.</param>
/// <exception cref="InvalidOperationException"></exception>
public void OnActionExecuting(ActionExecutingContext context)
{
context.HttpContext.Response.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
}
}
If you do the same but only on exception, use a IExceptionFilter:
public class CorrelationIdFilter : IExceptionFilter, IAsyncExceptionFilter
{
private readonly ILogger<CorrelationIdFilter> _logger;
/// <summary>
/// Initialize a new instance of <see cref="CorrelationIdFilter"/>
/// </summary>
/// <param name="logger">A logger</param>
public CorrelationIdFilter(ILogger<CorrelationIdFilter> logger)
{
_logger = logger;
}
/// <summary>
/// Called after an action has thrown an <see cref="T:System.Exception" />.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" />.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" /> that on completion indicates the filter has executed.
/// </returns>
public Task OnExceptionAsync(ExceptionContext context)
{
Process(context);
return Task.CompletedTask;
}
/// <summary>
/// Called after an action has thrown an <see cref="T:System.Exception" />.
/// </summary>
/// <param name="context">The <see cref="T:Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" />.</param>
public void OnException(ExceptionContext context)
{
Process(context);
}
private void Process(ExceptionContext context)
{
var e = context.Exception;
_logger.LogError(e, e.Message);
context.HttpContext.Response.Headers.Add("X-Correlation-Id", Guid.NewGuid().ToString());
if (e is InvalidOperationException)
{
context.Result = WriteError(HttpStatusCode.BadRequest, e);
}
else if (e.GetType().Namespace == "Microsoft.EntityFrameworkCore")
{
context.Result = WriteError(HttpStatusCode.BadRequest, e);
}
else
{
context.Result = WriteError(HttpStatusCode.InternalServerError, e);
}
}
private IActionResult WriteError(HttpStatusCode statusCode, Exception e)
{
var result = new ApiErrorResult(e.Message, e)
{
StatusCode = (int)statusCode,
};
return result;
}
}
/// <summary>
/// Api error result
/// </summary>
/// <seealso cref="Microsoft.AspNetCore.Mvc.ObjectResult" />
public class ApiErrorResult : ObjectResult
{
private readonly string _reasonPhrase;
/// <summary>
/// Initializes a new instance of the <see cref="ApiErrorResult"/> class.
/// </summary>
/// <param name="reasonPhrase">The reason phrase.</param>
/// <param name="value">The value.</param>
public ApiErrorResult(string reasonPhrase, object value) : base(value)
{
_reasonPhrase = reasonPhrase;
}
/// <inheritdoc />
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var reasonPhrase = _reasonPhrase;
reasonPhrase = reasonPhrase.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)[0];
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = reasonPhrase;
await base.ExecuteResultAsync(context);
}
}
And register your filter with :
services.AddMvc(configure =>
{
var filters = configure.Filters;
filters.Add<CorrelationIdFilter >();
})
I have had this issue for a while and I cannot make the determination whether this is actually a problem or not.
I have a controller method whose purpose is to add a document to a document list:
[Route("Documents/DocumentLists/{documentListId:int}")]
[HttpPost]
public async Task<IHttpActionResult> Add() {
CreateDocumentCommand createCommand = null;
IHttpActionResult actionResult = null;
// Check if the request contains multi-part/form-data.
if(!Request.Content.IsMimeMultipartContent()) {
return new SimpleErrorResult(Request, HttpStatusCode.UnsupportedMediaType, "Form is not multi-part.");
}
try {
var data = await Request.Content.ParseMultipartAsync();
.....code ellided
I'm registering the method with Autofac like this:
builder.RegisterType<DocumentListAdminAuthorizationFilter>().AsWebApiAuthorizationFilterFor<DocumentsController>(c => c.Add()).InstancePerLifetimeScope();
Because the controller method has the async keyword, I keep getting the warning:
"Because this call is not awaited, execution of the current method continues before the call is completed"
The code works, however, and users not in the appropriate roles cannot access the method. Users with the appropriate roles can add documents.
I don't completely understand how everything is working under the covers so that everything is functioning properly.
Edited - 2017-05-01
I am not getting a error per se. I am getting a warning. The code compiles and works.
Here is the filter:
public class DocumentListAdminAuthorizationFilter : IAutofacAuthorizationFilter {
readonly IDocumentListContainerRepository documentListRepository;
readonly IUserRepository userRepository;
readonly IWebUserSession userSession;
readonly ILog logger;
readonly IPeotscConfigurationSection configuration;
readonly IMediator mediator;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentListAdminAuthorizationFilter"/> class.
/// </summary>
/// <param name="userSession">The user session.</param>
/// <param name="userRepository">The user repository.</param>
/// <param name="documentListRepository">The document list repository.</param>
public DocumentListAdminAuthorizationFilter(ILog logger, IWebUserSession userSession, IPeotscConfigurationSection configuration,
IUserRepository userRepository, IDocumentListContainerRepository documentListRepository, IMediator mediator) {
this.userSession = userSession;
this.userRepository = userRepository;
this.documentListRepository = documentListRepository;
this.logger = logger;
this.configuration = configuration;
this.mediator = mediator;
}
/// <summary>
/// Called on authorization.
/// </summary>
/// <param name="actionContext">The action context.</param>
/// <exception cref="InvalidResourceRequestHttpRequestException"></exception>
/// <exception cref="NotAuthorizedHttpRequestException"></exception>
public void OnAuthorization(HttpActionContext actionContext) {
int documentListId = 0;
User user = null;
if(!Check.CanGetInteger(actionContext.RequestContext.RouteData.Values["documentListId"], out documentListId)) {
throw new InvalidResourceRequestHttpRequestException();
}
if(Check.NotNull<IWebUserSession>(userSession) && Check.NotEmpty(userSession.UserName)) {
throw new SessionHasExpiredHttpRequestException();
}
user = userRepository.GetUserByUserName(userSession.UserName);
if(ReferenceEquals(null, user)) {
throw new UserNotFoundHttpRequestException();
}
var documentList = documentListRepository.GetById(documentListId);
if (ReferenceEquals(null, documentList)) {
throw new DocumentListNotFoundHttpRequestException();
}
var isTemplateAdmin = userRepository.IsDocumentListAdmin(user, documentList);
if(!isTemplateAdmin) {
Exception exception = new Exception("User attempted to access administrator functionality on the document list to which they should not have had access.");
exception.Data.Add("UserId", user.Id);
exception.Data.Add("UserName", user.UserName);
exception.Data.Add("DocumentListId", documentList.Id);
logger.Error("Inappropriate access to document list functionality.", exception);
InappropriateAdministratorAccessEmailSendEvent eventMessage = new InappropriateAdministratorAccessEmailSendEvent {
User = user,
DocumentList = documentList
};
mediator.Publish(eventMessage);
throw new NotAuthorizedHttpRequestException();
}
}
public Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken) {
OnAuthorization(actionContext);
return Task.FromResult<object>(null);
}
}
I have an issue where I can't seem to send new data to the connected Signal R clients from a ChangedEventHandler. The docs says that I can get the hub context by using:-
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
However nothing gets sent to the clients (checked on fiddler) or any errors reported. My onchange event is wired up at the moment from Application_Start as I am creating a proof of concept. I should point out the hub does work on start up and retrieves the data from the initial GetAll call
protected void Application_Start()
{
...
_sqlTableDependency.OnChanged += _sqlTableDependency_OnChanged;
_sqlTableDependency.Start();
...
}
private void _sqlTableDependency_OnChanged(object sender, RecordChangedEventArgs<BiddingText> e)
{
switch (e.ChangeType)
{
case ChangeType.Insert:
foreach (var insertedCustomer in e.ChangedEntities)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
biddingTextList.Add(insertedCustomer);
}
break;
}
}
When I put a breakpoint on the hub context I get my ChatHub back.
My Javascript code:
$.connection.hub.url = "http://localhost:37185/signalr";
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
chat.client.initialText = function(data) {
var index;
//console.log(data.length);
for (index = 0; index < data.List.length; ++index) {
$('#list').append("<li>" + data.List[index].text + "</li>");
}
};
chat.client.addToList = function(data) {
console.log(data);
$('#list').append("<li>" + data.text + "</li>");
};
// Start the connection.
$.connection.hub.start({ jsonp: true }).done(function () {
chat.server.getAll(1831);
});
My Hub code:
public class ChatHub : Microsoft.AspNet.SignalR.Hub
{
private readonly IMediator mediator;
public ChatHub(IMediator mediator)
{
this.mediator = mediator;
}
public void GetAll(int saleId)
{
var model = mediator.Request(new BiddingTextQuery { SaleId = saleId});
Clients.Caller.initialText(model);
}
}
Not sure if this is relevant but the Clients.Connection.Identity is different everytime I use GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
Can anyone help?
I had some similar issues a while back setting up a Nancy API to publish some events to SignalR clients.
The core issue I had was that I had failed to ensure Nancy and SignalR was using the same DI container at the SignalR global level.
SignalR, as Nancy, has a default DependencyResolver that is used to resolve any dependencies in your hubs. When I failed to implement the same source of dependencies for Nancy and SignalR I basically ended up with two separate applications.
Small disclaimer: You have not posted your config code, so my solution here is based on some assumptions (as well as the following twitter answer from David Fowler when you reached out on twitter:
#rippo you have a custom dependency resolver and global has has another. You need to use one container
(https://twitter.com/davidfowl/status/635000470340153344)
Now some code:
First you need to implement a custom SignalR depependency resolver, and ensure it uses the same source of dependencies as the rest of your application.
This is the implementation I used for an Autofac container:
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LabCommunicator.Server.Configuration
{
internal class AutofacSignalrDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private ILifetimeScope LifetimeScope { get; set; }
public AutofacSignalrDependencyResolver(ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
var currentRegistrationSource = LifetimeScope.ComponentRegistry.Sources.FirstOrDefault(s => s.GetType() == GetType());
if (currentRegistrationSource != null)
{
((AutofacSignalrDependencyResolver)currentRegistrationSource).LifetimeScope = lifetimeScope;
}
else
{
LifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
}
public override object GetService(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetService(serviceType);
}
if (LifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetServices(serviceType);
}
if (LifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instances = base.GetServices(typedService.ServiceType);
if (instances != null)
{
return instances
.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerLifetimeScope()
.PreserveExistingDefaults()
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
}
Next, in your SignalR config, you need to assign the custom dependency resolver:
Note: My app was using owin, so you may not need the HubConfiguration bit, but you need the GlobalHost bit (which is the one I messed up when my stuff was not working).
var resolver = new AutofacSignalrDependencyResolver(container);
'Owin config options.
var config = new HubConfiguration()
{
Resolver = resolver,
EnableDetailedErrors = true,
EnableCrossDomain = true
};
GlobalHost.DependencyResolver = resolver;
'More owin stuff
app.MapHubs(config);
Hope this will help resolve your issue.
You need to keep track of the clients that connect to the hub, and then send them the new messages, something like this
This is a base class I wrote for my Hubs
/// <summary>
/// base class for Hubs in the system.
/// </summary>
public class HubBase : Hub {
/// <summary>
/// The hub users
/// </summary>
protected static ConcurrentDictionary<Guid, HubUser> Users = new ConcurrentDictionary<Guid, HubUser>();
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnConnected() {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user = Users.GetOrAdd(userName, _ => new HubUser {
UserId = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
/// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled">true, if stop was called on the client closing the connection gracefully;
/// false, if the connection has been lost for longer than the
/// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
/// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) {
try {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user;
Users.TryGetValue(userName, out user);
if (user != null) {
lock (user.ConnectionIds) {
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any()) {
HubUser removedUser;
Users.TryRemove(userName, out removedUser);
}
}
}
} catch {
//Bug in SignalR causing Context.User.Identity.Name to sometime be null
//when user disconnects, thus remove the connection manually.
lock (Users) {
HubUser entry = Users.Values.FirstOrDefault(v => v.ConnectionIds.Contains(Context.ConnectionId));
if (entry != null) entry.ConnectionIds.Remove(Context.ConnectionId);
}
}
return base.OnDisconnected(stopCalled);
}
private Guid RetrieveUserId() {
Cookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket decryptedCookie = FormsAuthentication.Decrypt(authCookie.Value);
var user = JsonConvert.DeserializeObject<User>(decryptedCookie.UserData);
return user.Id;
}
}
Then the Hub code is
/// <summary>
/// A hub for sending alerts to users.
/// </summary>
public class AlertHub : HubBase, IAlertHub {
/// <summary>
/// Sends the alert.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="userId">The user identifier.</param>
public void SendAlert(string message, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).sendAlert(message);
}
}
/// <summary>
/// Send alert to user.
/// </summary>
/// <param name="returnId">The return identifier.</param>
/// <param name="userId">The user identifier.</param>
public void ReturnProcessedAlert(Guid returnId, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).returnProcessedAlert(returnId);
}
}
}