I'm rather new to ASP.NET Core, and right now I am trying to get a grasp on how UrlHelper works in general.
In my controller, I want to create an absolute URL to another action in the same controller, e.g. http://localhost:PORT/api/controller/action. The question is now, how do I do it?
I have tried with the following:
var urlHelper = new UrlHelper(new ActionContext());
var url = urlHelper.Action("ACTION", "CONTROLLER");
Furthermore, what are those different contexts like ActionContext?
You really shouldn’t create a UrlHelper yourself. It’s likely that whatever context you are currently in, there is already an IUrlHelper instance available:
ControllerBase.Url inside of controllers.
PageModel.Url inside a Razor view.
ViewComponent.Url inside a view component.
So chances are, that you can just access this.Url to get an URL helper.
If you find yourself in a situation where that does not exist, for example when implementing your own service, then you can always inject a IUrlHelperFactory together with the IActionContextAccessor to first retrieve the current action context and then create an URL helper for it.
As for what that ActionContext is, it is basically an object that contains various values that identify the current MVC action context in which the current request is being handled. So it contains information about the actual request, the resolved controller and action, or the model state about the bound model object. It is basically an extension to the HttpContext, also containing MVC-specific information.
If you are running ASP.NET Core 2.2 or later, you can also use the LinkGenerator instead of the IUrlHelper inside your services which gives you an easier way to generate URLs compared to having to construct the helper through the IUrlHelperFactory.
Related
In a webapi/mvc Controller, a Controller will return a ActionResult<T> type, and it has a ExecuteResultAsync(ActionContext) method.
What is the usage of ExecuteResultAsync(ActionContext) method?
How the MVC will used the method? And in it, where is the ActionContext parameter from? from the Http request?
What is the usage of ExecuteResultAsync(ActionContext) method?
IActionResult is the result (object) returned by your controller action method. ASP.NET Core executes this result by invoking the IActionResult::ExecuteResultAsync(ActionContext) method.
Action method might return different kinds of IActionResult, such as JsonResult (which sends a json to client when executed), BadRequestResult(which sends 400 response when executed) and so on.
You can also create your own implementation of IActionResult. Just be aware that the ExecuteResultAsync(ActionContext) method should write bytes to the HTTP Response . For example, I created a custom IActionResult to return CSV file for your reference. This is a simple implementation that doesn't contains too much complicated logic.
Some action results are rather complicated that they introduce a new IActionResultExecutor<TResult> interface to deal with these process, for example, the ObjectResult employ an IActionResultExecutor<ObjectResult> to do that. When creating your own implementation, whether to use an IActionResultExecutor<ObjectResult> is up to you.
How the MVC will used the method?
WebApp developers don't need to invoke thisIActionResult::ExecuteResultAsync(ActionContext) method manually. This is a method that will be invoked by the MVC/RazorPage subsystem.
If you're interested, the whole process is:
a coming request received
match current request against the pre-defined routes (route table or graph). If matched :
we know the controller name, action name, and other route data.
Since we've known the controller name and action name, ASP.NET Core generates an instance of ActionDescriptor that describes the target C# action method, such as the parameters.
Since ASP.NET Core has known the Controller/Action and routes data, it creates an instance of IActionInvoker to invoke that action method pipline (including the filters, for more details, see official docs):
Action Method returns an instance of IActionResult
Before invoking the IActionResult::ExecuteResultAsync(ActionContext), invoke Result Filters OnResultExecuting() method.
invoke IActionResult::ExecuteResultAsync(ActionContext)
After that, invoke the Result Filters OnResultExecuted() method.
where is the ActionContext parameter from? from the Http request?
Firstly, HttpContext is built by the underlying server. It contains a Request property that mimics the HTTP Request.
Next, we'll get another two objects after selecting the action:
RouteData: the route data, e.g. current area name, current page name, e.t.c.
ActionDescriptor: a description about the current action that are matched with current route.
With the above three objects, ASP.NET Core creates the ActionContext by simply new it. For example, the IRouter-based Routing system creates an actionContext as below:( see source code)
// create action context
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
// create action invoker
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
if (invoker == null){ throw ...;}
// invoke the pipeline
return invoker.InvokeAsync();
I am working with two controllers, they both save a value to Session but only one of the Controller manages to maintain it's value.
The line of code that saves the value is
Session["LoginDate"] = <dateTimeObject>;
and this is the same in both Controllers. The Second controller gets called from the First Controller and while in the second controller, if I set the value of Session then we're ok until I get back in the calling controller. If I call the First controller only, the value can get set and be sent back to the client.
I have tried modifying the second config file to include
<sessionState mode="InProc" timeout="30" />
and have made sure they are at the same version of .NET, MVC, etc...
Any ideas as to how to debug this? What else should I check?
UPDATE
Is there a way to pass the session state from different servers or would usign cookies be better since the cookie will be on the client browser? The new discovery is that the second controller does an
Redirect("serverOfController_1");
The controller gets initialised by the MVC core, so that it has the correct references to the context of the current request. When you create an instance of a controller yourself, that won't have any context at all, so it can't use anything from the controller interface.
For a method in that controller to work in that context, it can't rely on anything in the controller interface. If you want to set a session variable from that method, you have to get the current context and access the Session object from that:
System.Web.HttpContext.Current.Session["LoginDate"] = <dateTimeObject>;
You can also copy the controller context from the current controller after you have created the instance. That way the controller that you created will have the same context as the current controller. Example:
SecondController second = new SecondController();
second.ControllerContext = ControllerContect;
second.SomeMethod();
I was just assigned to implement one functionality in project that uses Umbraco. My job is to basically generate specific XML and return it to user. However i cannot get it to work, because when i create new controller (i've tried creating
Controller, RenderMvcController and SurfaceController
) and method in it (also if i just create new method in existing controller), i get error 404 after typing url into browser. Example: I create TestController and method Index in it. I've tried combinations where TestController was derived from RenderMvcController or SurfaceController or just Controller. After compiling, etc. when i run
http://my_address/Test
or
http://my_address/Test/Index
i get 404 error from umbraco. I looked at another pages in umbraco that were already in project and they all are also configured somehow in umbraco web panel:
http://my_address/umbraco
I aslo tried adding new methods to existings controllers, but no luck (again 404 errors). I've never worked with umbraco and i don't know how to configure it. I just want to know if there is any way to create method which will be accessible at:
http://my_address/MyMethod
or
http://my_address/MyController/MyMethod
and would return just exactly what i will program it to (without any Views, Partial Views, etc. - i can set Headers and ContentType manually and my content is pure text) in an existing Umbraco project without having to deal with umbraco admin panel?
Thanks for any help :)
//Edit
My mind is officially blown... My response is culture dependent (i mean i pull different data from db depending on country), but it's not as simple as
CurrentCulture.CultureInfo
Umbraco is configured to return different culture based on domain extension (Germany for .de, Great Britain for .co.uk, and Dennmark for .dk - it's just a manual configuration in umbraco admin panel assigning different culture info and views to different hostnames). Regular controllers get this modified culture from
RenderModel.CurrentCulture
passed as argument to controller's method. Is there a way to create umbraco controller/method/anthing that will not have layout/model assigned to it (so i can display pure XML data i receive from external service) and still have access to umbraco's RenderModel's culture? What i am trying to create is if user types url:
http://my_address.de/myController/myMethod
my controller will get current culture, call external service passing culture as parameter and display received data without wrapping it in any views. Example:
public class myController : SomeBaseUmbracoControllerOrsomething
{
public string/XmlDocument/ActionResult myMethod(RenderModel model)
{
int countryId = myFunctionToTranslateCultureToCountryId(model.CurrentCulture);
return MethodThatCallsExternalServiceAndReturnsXml(countryId);
}
}
Sorry for confusion, but i've learned about this whole mess with countries just now...
You don't want to use
controller, because this is not picked up by umbraco routing process
you don't want to use RenderMvcController, because this is overkill
you don't want to use Surfacecontroller because you are not using a Child action or form.
What you need is a UmbracoApiController (http://our.umbraco.org/documentation/Reference/WebApi/) or is your umbraco version is PRE 6.1 then use /Base extention (http://our.umbraco.org/documentation/Reference/Api/Base/Index)
Or if you really want to skip ALL umbraco magic for a certain route, add the path to the web.config/AppSettings/umbracoReservedUrls.
I am try to integrate SagePayMvc.dll into a ASP.NET Web API project which requires ControllerContext.RequestContext to be passed in order to form the Notification Url.
Currently I am experiencing some difficulties in achieving this, I need to pass the ControllerContext.RequestContext from this web api controller:
public class PaymentStartController : ApiController
{
private PaymentRepository paymentRepository = new PaymentRepository();
private SagePayHelper sagePayHelper = new SagePayHelper();
public Order MakePaymentInitial(Payment payment)
{
Order order = new Order();
order = sagePayHelper.MakePayment(payment, context);
paymentRepository.InsertVendorTXCode(order.VendorTxCode);
paymentRepository.InsertInitialPaymentDetails(order, payment);
return order;
}
}
I have tried to add a public ControllerContext controllerContext = new ControllerContext() below the SagePayHelper instantiation and then subsequently added var context = controllerContext.RequestContext, the problem with this none of the methods inside RequestContext are instantiated either so when SagePayMvc arrives at the point of building the Notification Url which is done inside an IUrlResolver interface an error is thrown.
Is there a way of mocking up ControllerContext.RequestContext, I have previously used RhinoMocks or would it be more prudent to revert to the way I previously implemented SagePayMvc down in the forms project (the forms project is an MVC 4 application that serializes and sends the form data to the web api).
Any advise would be much appreciated.
ASP.NET Web API uses completely different runtime components from ASP.NET MVC for representing the context and request/response messages. It looks like the API you are using is heavily tied to ASP.NET MVC, which makes it really hard to reuse in ASP.NET Web API unless you initialize the ASP.NET MVC content doing manual mappings. I think it would be easier for you to just use ASP.NET MVC for invoking that method expecting the MVC context.
In my current project we have a notification system. When an oject is added to another objects collection, an email is sent to those who are subscibed to the parent object. This happens on the object layer and not in the View or Controller.
Here's the problem:
Although we can say who created what with what information in the email, we cannot embed links to those objects in the email because in the object layer there is no access to a UrlHelper. To construct a UrlHelper you need a RequestContext, which again does not exist on the object layer.
Question:
I want to make a helper class to create the url's for me. How can I create an object that will generate these urls without a request context? Is it possible?
The problem is compounded by the fact that you don't want a relative URL in an email, you want an absolute email so you need to hard-code the domain too because there is no request to grab it from.
Another factor is that emails can outlive the current site structure by months or years so you need a kind of permalink, and thus a way to associate multiple Urls with a single action (additional routes). This latter issue is also a factor in SEO where you don't want to leave any page behind.
For now a static method on your controller UrlToActionX(params) sitting next to the method ActionX seems like the simplest workaround. All it does is the appropriate string.Format(...) on the id's of the strongly-typed parameters to generate the permanent Url. Add a static domain on the front, or a domain from the user object (since you know which domain they visit when they come to your site) and you have your email link.
It's not ideal but at least you now have only one place to maintain the Url generation.
IMHO: When it comes to permanent links to a changing web site sometimes it's better to rely on "configuration over convention". :-)
I'm not aware of a way to do this, you MUST have access to the routes at the very least to make your own helper. Unless your business objects know about the registered routes, you can't get away from doing some hard-coding.
Here is how you might limit the hard-coding of urls though...
Code in a url with all the relevant bits in your object's methods..
class Event
{
public void SendEmail()
{
var url = string.Format("http://myurl.com/r/Event?eventId={0}", EventId);
//send emails...
}
}
Note the /r/Event piece of the url. This would be a map to a RController that would be responsible for taking arbitrary, made-up links and sending a 301 Permanent Redirect and going through the route engine to create a real url using the current routes. This way you are only hard-coding a utility controller url and not to the ever evolving controller actions of your real pages.
class RController : Controller
{
public ActionResult Event(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Details", "Event", new { eventId = eventId });
return null;
}
public ActionResult Register(int eventId)
{
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.RedirectLocation = Url.Action("Register", "Event", new { eventId = eventId });
return null;
}
}
It just feels a bit better than hard-coding a bunch of different controllers/actions that you might decide to rename later. Think of it as your own little TinyUrl like service.
You could define an interface with a method that takes whatever information is necessary to create a URL (object ids or whatever) and returns a URL. Write an implementation of that interface that uses the UrlHelper to do this work, and then supply this to your object layer (ideally with an IoC container).
You could use:
VirtualPathUtility.ToAbsolute(string.Format("~/r/Event?eventId={0}", id))
to resolve the url. Still not nice though.