Hi I have a custom requirement handler with accepts the AuthorizationHandlerContext context parameter
When i debug, i can see that the context object contains
Context.Resources.ActionDescription.ActionName
But when writing the code i cant go beyond
Context.Resources
Seems the lower levels are not exposed. I want to get the action name and controller name that called the handler. How do i do this?
var mvcContext = context.Resource as AuthorizationFilterContext;
var descriptor = mvcContext?.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
var actionName = descriptor.ActionName;
var ctrlName = descriptor.ControllerName;
}
After upgrading to dotnet 5, the solution I was successfully using from Carsten above stopped working. The following workaround now works for me:
var routeValues = (context.Resource as HttpContext).Request.RouteValues;
var controllerName = routeValues["controller"].ToString();
var actionName = routeValues["action"].ToString();
Note this should include some null checks etc. the above is a barebones example.
Even though the question is tagged for asp.net-mvc, I wanted to add that the answer by #AdemCaglin does not work for Web API controllers. The following code works for both, API and MVC controllers:
var endpoint = context.Resource as RouteEndpoint;
var descriptor = endpoint?.Metadata?
.SingleOrDefault(md => md is ControllerActionDescriptor) as ControllerActionDescriptor;
if (descriptor == null)
throw new InvalidOperationException("Unable to retrieve current action descriptor.");
var controllerName = descriptor.ControllerName;
var actionName = descriptor.ActionName;
Related
services.AddHeaderPropogation(o =>
{
o.Headers.Add("Id")
o.Headers.Add("Id", context => {
return new StringValues(Guid.NewGuid().ToString())
});
});
The above code helps me to create a header called id if it doesnt exist with a new guid and if it exists, it would just use the value. This is using Microsoft Header Propogation nuget package. And it works.
But now i have a requirement to add this to Azure Application insights, but the standard way of doiing it only works when the incoming request has headers. If the new GUID is created, it doesnt trigger the ITelemetryInitializer call.
Because for adding Telmetry custom values, we have a class which inherits ITelemtryInitializer and inside that i do call to Request.Headers like below:
var requestTelemetry = telemetry as RequestTelemetry
if(context.Request.Headers.TryGetValue(id, out var value))
requestTelemtry.Properties[id] = value.ToString()
But the above line is never triggered since the Request.Headers never had this id. This id will be created only by the middleware when the api calls the next service.
So my question, is there a way to call the telemetry classes from the Startup> ConfigfureServices and inside the HeaderPropogation code, so that as soon as the new GUID is created, i can add it to telemtry. All the examples of adding to telemetry shows either from controller or DI. How to call it from the Startup itself ?
Or is there a better way to achieve the same ?
Let me post the solution I found. I didnt need to have a telemetryinitializer class to populate the guid in another class. I wanted it to be added as soon as we create it, so this is how i modified th header propogiation code in services in startup.
services.AddHeaderPropagation(options =>
{
var correlationId = "YourId";
options.Headers.Add(correlationId, context => {
var requestTelemetry = context.HttpContext.Features.Get<RequestTelemetry>();
if (context.HttpContext.Request.Headers.TryGetValue(correlationId, out var value))
{
requestTelemetry.Properties[correlationId] = value.ToString();
return value.ToString();
}
else
{
var guidId = Guid.NewGuid().ToString();
requestTelemetry.Properties[correlationId] = guidId;
return new StringValues(guidId);
}
});
});
Key for me has been the realization i can get RequestTelemetry anywhere we want in the code with this properties option.
var requestTelemetry = context.HttpContext.Features.Get<RequestTelemetry>();
I am using a web api 2 and creating a message object to save to the database. This message object needs to have the current user stored on it as an application user type.
My code looks like this:
var UserManager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
var currentUser = RequestContext.Principal;
var currentUserName = currentUser.Identity.Name;
var currentApplicationUser = UserManager.FindByName(currentUserName);
// I perhaps want to dispose of the user context?
// UserManager.Dispose();
globalMessage.sentBy = currentApplicationUser;
db.GlobalMessages.Add(globalMessage);
The last line is throwing the error: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
Is there another way around this. Otherwise, I imagine, I could call an action which gets the current user, redirect to another action from the action with this user as an arg and then perform the update?
I worked it out:
var currentUser = RequestContext.Principal;
var currentUserName = currentUser.Identity.Name;
var sender = db.Users.Where(u => u.UserName == currentUserName).FirstOrDefault();
globalMessage.sentBy = sender;
db.GlobalMessages.Add(globalMessage);
db.SaveChanges();
The problem is you're attaching to your db context an entity (currentApplicationUser) that already is attached to another context instance that is alive too, so a solution could be to find the user using the same context, in your case it would be something like:
var UserManager = Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
var currentUser = RequestContext.Principal;
var currentUserName = currentUser.Identity.Name;
var currentApplicationUser =db.AspNetUsers.FirstOrDefault(u=>u.UserName==currentUserName);
globalMessage.sentBy = currentApplicationUser;
db.GlobalMessages.Add(globalMessage);
I am trying to load the Exchange Web Services DLL at runtime and connect to a mailbox. I am following this guide: Using Reflection to load unreferenced assemblies at runtime in C#
The code so far:
var DLL = Assembly.LoadFile(#"Microsoft.Exchange.WebServices.dll");
var theType = DLL.GetType("Microsoft.Exchange.WebServices.Data.ExchangeService");
var c = Activator.CreateInstance(theType);
var method = theType.GetMethod("AutodiscoverUrl");
method.Invoke(c, new object[] { #"anyvalid#email.com" });
After that code I am lost. How do I use the ExchangeService to bind a Mailbox object using a FolderId? EWS Managed API is not an option for my server and application.
This is the Powershell script equivalent code that I am trying to implement in ASP.NET:
$MailboxName = "account#domain"
$dllpath = "Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.AutodiscoverUrl("anyvalid#email.com")
$mbfolderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$MsgRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$mbfolderid)
Using reflection is tedious. You are on the right track though. The following code shows how you can bind to the "Inbox" folder and get the subjects of the first 10 email messages.
Notice how I use the dynamickeyword so I do not have to call MethodInfo.Invoke to call instance methods on the reflected types.
string mailboxName = "...";
// Get value for enum WellKnownFolderName.Inbox.
var wellKnownFolderNameType = assem.GetType("Microsoft.Exchange.WebServices.Data.WellKnownFolderName");
var rootFolderName = wellKnownFolderNameType
.GetField("Inbox")
.GetValue(null)
;
// Create requested mailbox and folderid for Inbox-folder for the requested mailbox.
var mailboxType = assem.GetType("Microsoft.Exchange.WebServices.Data.Mailbox");
dynamic mailbox = Activator.CreateInstance(mailboxType, new object[] { mailboxName });
var folderIdType = assem.GetType("Microsoft.Exchange.WebServices.Data.FolderId");
dynamic folderId = Activator.CreateInstance(folderIdType, rootFolderName, mailbox);
// Bind to the Inbox-folder for the requested mailbox.
var folderType = assem.GetType("Microsoft.Exchange.WebServices.Data.Folder");
var bindMethod = folderType.GetMethod("Bind", new Type[] { serviceType, folderIdType });
dynamic folder = bindMethod.Invoke(null, new object[] { service, folderId });
// Get 10 first mailitems
var itemViewType = assem.GetType("Microsoft.Exchange.WebServices.Data.ItemView");
dynamic itemView = Activator.CreateInstance(itemViewType, 10);
dynamic findItemsResults = folder.FindItems(itemView);
foreach (dynamic item in findItemsResults.Items)
{
Console.WriteLine((string) item.Subject);
}
... but the same extension method works when the application itself is executing. The UrlHelper extension method itself looks like this:
public static string CategoryLandingPage(this UrlHelper helper, string seoCategoryName)
{
return helper.RouteUrl("Category", new { area = "SoAndSo", controller = "SoAndSo", action = "Category", seoCategoryName = seoCategoryName }, "http");
}
I register that particular route like this in my SoAndSoAreaRegistration class:
context.MapRoute(
"Category",
"category/{seoCategoryName}",
new { area = "SoAndSo", controller = "SoAndSo", action = "Category", seoCategoryName = string.Empty }
);
... and I have dropped a breakpoint on that registration to ensure that it gets hit by the test runner, and it does.
When I run the test, I get an ArgumentException, "A route named 'Category' could not be found in the route collection. Parameter name: name".
My guess is that we do not need to specify the route name and enough route parameters (area/controller/action/category name) to construct the route in its entirety as we're doing here, but I can't figure out where the route name disappears to during testing. Removing the category name eliminates the exception and allows the test to pass, but I would still like to understand where the route name disappears to when I'm testing. Simplifying the code like so still blows up at runtime:
public static string CategoryLandingPage(this UrlHelper helper, string seoCategoryName)
{
return helper.RouteUrl("Category");
}
If I dig through the route collection at runtime, I can find the category route, but there is no evidence of a .Name property, nor do I see the route's name ("Category" with a capital C) anywhere among the UrlHelper's properties (apologies for the goofy obfuscation; better safe than sorry):
Does anyone know how I can write unit tests which hit UrlHelper extension methods which reference routes by their name? Thanks!
Update -
I'll add some of the test initialization, most of which I got from this popular question, lightly modified to account for the fact that the application I'm working with is separated into multiple MVC areas:
private SoAndSoController CreateController()
{
var service = new Mock();
var cookieMgr = new Mock();
var logger = new Mock();
var allRoutes = new RouteCollection();
MvcApplication.RegisterRoutes(allRoutes);
var soAndSoAreaRegistration = new SoAndSoAreaRegistration();
var soAndSoAreaRegistrationContext = new AreaRegistrationContext(soAndSoAreaRegistration.AreaName, new RouteCollection());
soAndSoAreaRegistration.RegisterArea(soAndSoAreaRegistrationContext);
soAndSoAreaRegistrationContext.Routes.ForEach(r => allRoutes.Add(r));
var request = new Mock<HttpRequestBase>();
request.SetupGet(x => x.ApplicationPath).Returns("/");
request.SetupGet(x => x.Url).Returns(new Uri("http://localhost/a", UriKind.Absolute));
request.SetupGet(x => x.ServerVariables).Returns(new System.Collections.Specialized.NameValueCollection());
var response = new Mock<HttpResponseBase>();
response.Setup(x => x.ApplyAppPathModifier("/post1")).Returns("http://localhost/post1");
var context = new Mock<HttpContextBase>();
context.SetupGet(x => x.Request).Returns(request.Object);
context.SetupGet(x => x.Response).Returns(response.Object);
var controller = new SoAndSoController(service.Object, cookieMgr.Object, null, logger.Object, null);
controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);
controller.Url = new UrlHelper(new RequestContext(context.Object, new RouteData()), allRoutes);
return controller;
}
I figured it out. I needed to pass my RouteCollection into the AreaRegistrationContext, rather than passing it a new RouteCollection:
var productFindingAreaRegistrationContext = new AreaRegistrationContext(productFindingAreaRegistration.AreaName, allRoutes);
But that was causing this line to blow up:
productFindingAreaRegistrationContext.Routes.ForEach(r => allRoutes.Add(r));
However now this line was no longer needed, so I commented it out. Voila.
How can I, when executing a controller action, take a Uri (not the one requested) and invoke the action from the controller that would have been executed had that Uri been the one that was called? I can't simply redirect to that action as I need it to happen in the same request context.
Assuming you have access to the HttpContext (and I suppose you do since you are asking) you could:
var routeData = new RouteData();
// controller and action are compulsory
routeData.Values["action"] = "index";
routeData.Values["controller"] = "foo";
// some additional route parameter
routeData.Values["foo"] = "bar";
IController fooController = new FooController();
var rc = new RequestContext(new HttpContextWrapper(HttpContext), routeData);
fooController.Execute(rc);
Personally I use this approach for handling errors inside my application. I put this in Application_Error and execute an error controller for custom error pages staying in the context of the initial HTTP request. You could also place complex objects inside the routeData hash and you will get those complex objects back as action parameters. I use this to pass the actual exception that occurred to the error controller action.
UPDATE:
In order to parse an URL to its route data tokens taking into account current routes you could:
var request = new HttpRequest(null, "http://foo.com/Home/Index", "id=1");
var response = new HttpResponse(new StringWriter());
var httpContext = new HttpContext(request, response);
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
var values = routeData.Values;
var action = values["action"];
var controller = values["controller"];
For the correct answer, I'd prefer do something like this to let MVC handle creating controllers rather than creating myself.
var routeData = new RouteData();
// controller and action are compulsory
routeData.Values["action"] = "index";
routeData.Values["controller"] = "foo";
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
var requestContext = new RequestContext(new HttpContextWrapper(yourHttpContextObject), routeData);
var controller = factory.CreateController(requestContext, "FooController");
try
{
controller.Execute(requestContext);
}
finally
{
factory.ReleaseController(controller);
}
This would assure you that your Foo controller is getting the same behavior as other controllers.
Any reason you can't push the code you are calling into a controller-independent class? Cross-calling controllers sounds like a bit of a WTF to me.