In ASP.NET 4.x, there is a ReflectedControllerDescriptorclass which resides in System.Web.Mvc. This class provides the descriptor of a controller.
In my previous applications, I used to do this:
var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actions = (from a in controllerDescriptor.GetCanonicalActions()
let authorize = (AuthorizeAttribute)a.GetCustomAttributes(typeof(AuthorizeAttribute), false).SingleOrDefault()
select new ControllerNavigationItem
{
Action = a.ActionName,
Controller = a.ControllerDescriptor.ControllerName,
Text =a.ActionName.SeperateWords(),
Area = GetArea(typeNamespace),
Roles = authorize?.Roles.Split(',')
}).ToList();
return actions;
The problem is I can't find any equivalent of this class in ASP.NET Core. I came across IActionDescriptorCollectionProviderwhich seems to provide limited details.
The Question
My goal is to write an equivalent code in ASP.NET Core. How do I achieve that?
Your help is really appreciated
I came across IActionDescriptorCollectionProvider which seems to
provide limited details.
Probably you don't cast ActionDescriptor to ControllerActionDescriptor. Related info is here
My goal is to write an equivalent code in ASP.NET Core. How do I
achieve that?
Here is my attempt in ConfigureServices method:
var provider = services.BuildServiceProvider().GetRequiredService<IActionDescriptorCollectionProvider>();
var ctrlActions = provider.ActionDescriptors.Items
.Where(x => (x as ControllerActionDescriptor)
.ControllerTypeInfo.AsType() == typeof(Home222Controller))
.ToList();
foreach (var action in ctrlActions)
{
var descriptor = action as ControllerActionDescriptor;
var controllerName = descriptor.ControllerName;
var actionName = descriptor.ActionName;
var areaName = descriptor.ControllerTypeInfo
.GetCustomAttribute<AreaAttribute>().RouteValue;
}
Related
I can see a lot of ways to do it online but most of them are messy, for me I was using these two ways
Using scopes, I did one for mobile and another one for the website
var webScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<WebAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
var mobileScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<MobileAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
And it worked because I had two different ways in authorizing the api calls, as you can see I had a Mobile Authorize and a Web Authorize so my api calls would look something like this:
[HttpGet]
[Route("something")]
[WebAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<Dto> Getsomething()
{
return await unitOfWork.GetService<ISomething>().GetSomething();
}
Issues I face when using scopes is that all calls that have web authorize will share the same headers so for the special calls I used another way to add custom headers.
Using apiDescription.RelativePath, and I will check it if the relative path is equal to the api call I want to add that custom header, example:
[HttpPost]
[Route("rename")]
[InHouseAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<HttpResponseMessage> RenameDevice()
{
HttpRequestMessage request = Request ?? new HttpRequestMessage();
String deviceName = request.Headers.GetValues("deviceName").FirstOrDefault();
String deviceGuid = request.Headers.GetValues("deviceGuid").FirstOrDefault();
await unitOfWork.GetService<IDeviceService>().RenameDevice(deviceGuid, deviceName);
await unitOfWork.Commit();
return new HttpResponseMessage(HttpStatusCode.OK);
}
And then I would add to the AddRequiredHeaderParameter.cs the following
if (apiDescription.RelativePath.Contains("device/rename"))
{
operation.parameters.Add(new Parameter
{
name = "deviceGuid",
#in = "header",
description = "Add the Device Guid",
type = "string",
required = false
});
operation.parameters.Add(new Parameter
{
name = "DeviceName",
#in = "header",
description = "Add the Device Name",
type = "string",
required = false
});
}
At first this was convenient and good enough fix but things are turning ugly as I'm adding a lot of calls that need custom headers and if the same URL have a Get and Post then it will even get uglier.
I am searching for the best way to deal with this issue.
It's possible to use attribute [FromHeader] for web methods parameters (or properties in a Model class) which should be sent in custom headers. Something like this:
[HttpGet]
public ActionResult Products([FromHeader(Name = "User-Identity")]string userIdentity)
For me it looks like the easiest solution. At least it works fine for ASP.NET Core 2.1 and Swashbuckle.AspNetCore 2.5.0.
I am not seeing much information on porting a asp.net mvc 4.6 application to the asp.net core 2.1 Tons of 1.0 to 2.0 and 2.0. to 2.1 articles, but it is a bit steep in changes.
Ok, I have been all over google and stackoverflow and what I am trying/wanting to do is convert an older .net application and bring it into .net core.
Some big culprits I see as problems are the following:
HttpContext.Current
HttpContext.Current.Request.Url.AbsolutePath
HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)
private static string CollectionToHtmlTable(HttpCookieCollection collection)
I was seeing these articles which are helpful, but much to be desired.
https://www.carlrippon.com/httpcontext-in-asp-net-core/
https://dotnetcoretutorials.com/2017/01/05/accessing-httpcontext-asp-net-core/
Inside Controller it seems the easiest, but most of this HttpContext.Current stuff is sprinkled around various places domain libraries etc..
So in both url's above it seems that using this line below in the startup.cs
however, I don't have a startup.cs outside of main asp.net core
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Then also in startup I seen this article
https://www.strathweb.com/2016/12/accessing-httpcontext-outside-of-framework-components-in-asp-net-core/
//one of many things added to project
services.AddHttpContextAccessor();
I realize that several questions have been asked in the last couple of years - here is to hoping that some brave soul has explored this stuff and has some advise/answers to how to migrate this stuff over.
Method to Port: #1
public Service(string key, LogRecord.CallType callType, string operation, string opArg, string opResponse)
{
this.Key = string.IsNullOrEmpty(key)
? HttpContext.Current != null ? "Inbound/WebHttp: " + HttpContext.Current.Request.Url.AbsolutePath : string.Empty
: key;
this.CallType = Enum.GetName(typeof(LogRecord.CallType), callType);
this.URL = HttpContext.Current != null && callType == LogRecord.CallType.WebHttp
? HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)
: string.Empty;
this.Operation = operation;
this.OpArg = opArg;
this.OpResponse = opResponse;
}
Another: #2
private static string CollectionToHtmlTable(HttpCookieCollection collection)
{
// Converts HttpCookieCollection to NameValueCollection
var nvc = new NameValueCollection();
foreach (string item in collection)
{
var httpCookie = collection[item];
if (httpCookie != null)
{
nvc.Add(item, httpCookie.Value);
}
}
return CollectionToHtmlTable(nvc);
}
The problem is, you are currently using it wrong.
Inside Controller it seems the easiest, but most of this HttpContext.Current stuff is sprinkled around various places domain libraries etc..
A Domain Libarary should be independent from the frontend. It just doesn't matter if it's a website with Session or a statefull WPF App. Or a RestFull Api.
That said, a relative Quick Fix would be to inject the HttpContext into your domain class.
public DomainService(IHttpContextAccessor httpContext)
{
_httpContext = httpContext;
}
You can then obtain your DomainService via Dependency Injection. If the creation is complex, you are looking for a Factory.
Or you could create the Service yourself in your ActionMethod (Of course in this case you need need to change the constructor of the Service to HttpContext:
public IActionResult Foo()
{
var service = new DomainService(HttpContext)
}
That said: it was removed for a reason. Please do not rely on HttpContext in your Domain! Create a business object and give it as parameter to the domain instead.
Not sure if it is the best way, but I have created a BusinessContext class and registered it as scoped. A factory is responsible for creating it with the needed data.
DON'T DO THIS:
If you are really crazy, maybe this could help you for migration.
Posting this on request of OP, so it's not a full answer. You can reconstruct the URI from the IHttpContextAccessor like so:
IHttpContextAccessor context; // injected
var request = context.HttpContext.Request;
var uriBuilder = new UriBuilder();
uriBuilder.Scheme = request.Scheme;
uriBuilder.Host = request.Host.Host;
if (request.Host.Port.HasValue)
{
uriBuilder.Port = request.Host.Port.Value;
}
uriBuilder.Path = request.Path;
if (request.QueryString.HasValue)
{
uriBuilder.Query = request.QueryString.Value.Substring(1);
}
var requestUri = uriBuilder.Uri;
Likewise, you can access the IRequestCookieCollection via:
var collection = context.HttpContext.Request.Cookies
foreach (KeyValuePair<string, string> cookie in collection)
I need to get the controller name from my route and this I can do if using standard routing code in WebApiConfig.
However, if I am using routing attributes it starts to get a little difficult, especially when trying to version.
Example: If I call an api/terms/bonuses and I have a BonusController and BonusV2Controller and a BonusV3Controller, this code returns the latest controller version 3. That's ok, I can live with that returning the latest and greatest version as a default.
var attributedRoutesData = request.GetRouteData().GetSubRoutes();
var subRouteData = attributedRoutesData.FirstOrDefault();
var actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
//This seems to get latest controller name. ie. V2
controllerName = actions[0].ControllerDescriptor.ControllerName;
Now if I request a version 1, for simplicity I'll use a querystring and call api/terms/bonuses?v=2
So this code no longer works (obviously).
How do I get the V2 controller name?
If I abandon routing attributes and just use WebApiConfig routing, this code works happily.
HttpControllerDescriptor controllerDescriptor = null;
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = (string)routeData.Values["controller"];
UPDATE:
Here is my full selector code.
IDictionary<string, HttpControllerDescriptor> controllers = GetControllerMapping();
var attributedRoutesData = request.GetRouteData().GetSubRoutes();
var subRouteData = attributedRoutesData.LastOrDefault(); //LastOrDefault() will get PeopleController, FirstOrDefault will get People{version}Controller which we don't want
var actions = (ReflectedHttpActionDescriptor[])subRouteData.Route.DataTokens["actions"];
var controllerName = actions[0].ControllerDescriptor.ControllerName;
//For controller name without attribute routing
//var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor oldControllerDescriptor;
if (controllers.TryGetValue(controllerName, out oldControllerDescriptor))
{
//TODO: Different techniques for handling version api requests.
var apiVersion = GetVersionFromQueryString(request);
//var version = GetVersionFromHeader(request);
//var version = GetVersionFromAcceptHeaderVersion(request);
//var version = GetVersionFromMediaType(request);
if (!String.IsNullOrEmpty(apiVersion))
{
var newControllerName = String.Concat(controllerName, "V", apiVersion);
HttpControllerDescriptor newControllerDescriptor;
if (controllers.TryGetValue(newControllerName, out newControllerDescriptor))
{
return newControllerDescriptor;
}
}
return oldControllerDescriptor;
}
return null;
var subRouteData = request.GetRouteData().GetSubRoutes().LastOrDefault();
if (subRouteData != null && subRouteData.Route != null)
{
var actions = subRouteData.Route.DataTokens["actions"] as HttpActionDescriptor[];
if (actions != null && actions.Length > 0)
{
controllerName = actions[0].ControllerDescriptor.ControllerName;
}
}
At last I found it:
filterContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName
Goal: test that a given url returns a given controller function.
In the process, I've broken into the routing system and I can't figure out how to test routes (or, for that matter, find the controller corresponding to the route :-/).
Sample code, which doesn't work:
[Test]
public void kick_the_tires()
{
var rc = new RouteCollection();
Infrastructure.RouteRegistry.RegisterRoutes(rc);
// get the route corresponding to name.
var got = rc["name"];
var expected = //What? foo is an internal type that can't be instantiated.
Assert.AreEqual(foo, frob);
}
edit: Using the linked blog post from Simon for the stub class.
[TestCase("/", "~/", "Home", "Index")]
[TestCase("/", "api/command", "Other", "command")]
internal void stub_mocker(string apppath, string route, string expected_controller,\
string expected_action)
{
var rc = new RouteCollection();
Infrastructure.RouteRegistry.RegisterRoutes(rc);
var httpmock = new StubHttpContextForRouting(
appPath: apppath,
requestUrl: route);
// this always returns null for everything but the Index case.
var routeData = rc.GetRouteData(httpmock);
var controller = routeData.Values["controller"];
var action = routeData.Values["action"];
Assert.AreEqual(expected_controller, controller);
Assert.AreEqual(expected_action, action);
}
All you are testing right now is if the routes are added to the collection, by accessing it by the route name, and not if the expected route will return given a virtual path. You need to obtain the route data as returned by the RouteCollection with a HttpContext.
Best way would be to use a mock or a stub for the HttpContext (or HttpContextBase) and call the RouteCollection's GetRouteData(HttpContextBase) method and inspect the route data.
There is a good example of this in Brad Wilson's blog:
http://bradwilson.typepad.com/blog/2010/07/testing-routing-and-url-generation-in-aspnet-mvc.html
Edit:You cannot get a controller instance from the RouteData itself. However, RouteData should give you enough information to know which controller will be instantiated. For example, if you have a controller at MyProject.Controllers.HomeController with an action Home, this should hold true in your test (using xUnit and Moq):
// Prepare
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
context.SetupGet(c => c.Request).Returns(request.Object);
context.SetupGet(c => c.Response).Returns(response.Object);
context.SetupGet(c => c.Session).Returns(session.Object);
context.SetupGet(c => c.Server).Returns(server.Object);
request.SetupGet(r => r.HttpMethod).Returns("GET");
request.SetupGet(r => r.PathInfo).Returns(String.Empty);
request.SetupGet(r => r.AppRelativeCurrentExecutionFilePath).Returns("~/Home");
var expectedHandler = typeof (HomeController).GetMethod("Index", Type.EmptyTypes);
var data = RouteTable.Routes.GetRouteData(context.Object);
Assert.NotNull(data);
var handler = (MethodInfo) data.DataTokens["actionMethod"];
Assert.Equal(expectedHandler, handler);
I've had prety good experience with MVCContrib's Testhelper
Take a look at this test of testhelper.
Saves a lot of hassles around stubbing HttpContext etc.
Also if you are on MVC4, have a look at this Nuget package which is a fork for MVC4.
I'm creating a really simple ViewResult subclass called JavaScriptViewResult that, when executing, calls the base implementation and then sets the Content-Type of the response to text/javascript. In trying to unit test this class, I'm running across a slew of difficulties fulfilling all of the dependencies of the ASP.NET MVC stack.
Here is what my unit test, which uses Rhino, looks like so far:
[TestMethod]
public void TestExecuteAction()
{
var request = MockRepository.GenerateMock<HttpRequestBase>();
request.Expect(m => m.Url).Return(new Uri("/Test/JavaScript", UriKind.Relative));
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Request).Return(request);
var controller = MockRepository.GenerateMock<ControllerBase>();
var virtualPathProvider = MockRepository.GenerateMock<VirtualPathProvider>();
var routeCollection = new RouteCollection(virtualPathProvider);
routeCollection.MapRoute("FakeRoute", "Test/JavaScript", new { controller = "Test", action = "JavaScript" });
var routeData = routeCollection.GetRouteData(httpContext);
var context = new ControllerContext(httpContext, routeData, controller);
var viewResult = new JavaScriptViewResult();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}
The latest exception when running the test is a NullReferenceException deep within the bowels of System.Web.Routing.Route.GetRouteData(HttpContextBase httpContext).
How do I set up all of the dependencies for executing a ViewResult? Are there any techniques for making this simpler? Alternately, is there a different way I can utilize the MVC view engine to generate JavaScript that will set the proper Content-Type for the response?
I figured out how to meet the minimum requirements of ViewResult. One problem I was encountering was mocking the process of finding the view. This was avoidable by ensuring that the View property of my object was populated. Here is my working test:
[TestMethod]
public void TestExecuteAction()
{
var response = MockRepository.GenerateStub<HttpResponseBase>();
response.Output = new StringWriter();
var httpContext = MockRepository.GenerateMock<HttpContextBase>();
httpContext.Expect(m => m.Response).Return(response);
var routeData = new RouteData();
routeData.Values.Add("action", "FakeAction");
var context = new ControllerContext(httpContext, routeData, MockRepository.GenerateMock<ControllerBase>());
var viewResult = new JavaScriptViewResult();
viewResult.View = MockRepository.GenerateMock<IView>();
viewResult.ExecuteResult(context);
Assert.AreEqual("text/javascript", context.HttpContext.Response.ContentType);
}