.NET Core 3 using IUrlHelper in ControllerBase - asp.net-core-webapi

I have a NET Core 3.1 REST API in and have configured the start up with services.AddControllers since I don't need views etc; it is also using endpoint routing.
However, I cannot generate the urls for newly created resources since the ControllerBase.Url property is null.
The code looks a bit like this..
pubic IActionResult Post(OrderRequest request)
{
var order = service.CreateOrder(request);
var url = Url.Link("Get", new { id = order.Id });
return new CreatedResult(url, result);
}
So the questions are:
Should I be injecting LinkGenerator instead.
How can I change startup so that ControllerBase.Url is populated
Is this a bug?

Related

Set custom route using OData 8

Recently I updated to OData 8.0.10. I added this in my Startup.cs file:
services.AddRouting();
services.AddControllers().AddOData(opt =>
opt.AddRouteComponents("odata", GetEdmModel()).Filter().Select().OrderBy().Count());
where
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Project>("Project");
return builder.GetEdmModel();
}
I have this small controller
[Route("api/[controller]")]
[ApiController]
public class ProjectController : ControllerBase
{
[HttpGet]
[EnableQuery(PageSize = 20)]
public IQueryable<Project> GetAsync()
{
var projects = _projectRepository.GetAll();
return projects;
}
[HttpGet("{id}", Name = "GetProjectById")]
public async Task<ActionResult> GetAsyncById(long id)
{
var project = await _projectService.GetProjectByIDAsync(id);
return Ok(project);
}
[HttpPatch("{id}", Name = "PatchProjectById")]
public async Task<ActionResult> PatchProject(long id, [FromBody] ProjectPatchDetails projectPatch)
{
var project = await _projectRepository.GetAsync(id);
var updated = await _projectService.UpdateProjectAsync(id, project, projectPatch);
return Ok(updated);
}
}
that has three endpoints, one of them is annotated by [EnableQuery] and the rest aren't. When I access api/project?$count=true&$skip=0&$orderby=CreateDate%20desc, I get a paged info (20 records) but I don't get the #odata.context and #odata.count. If I access /odata/project?$count=true&$skip=0&$orderby=CreateDate%20desc, with odata/ prefix, it gives me #odata.context and #odata.count. I tried changing AddRouteComponents to AddRouteComponents("api", GetEdmModel()) but in this case I get the following error:
"The request matched multiple endpoints. Matches: MyApp.Api.Controllers.ProjectController.GetAsync (MyApp.Api) MyApp.Api.Controllers.ProjectController.GetAsync (MyApp.Api)"
I have multiple questions in this case:
Is there a way to reroute odata to api, make /odata prefix as /api and make it work?
Should I make another controller that will store all OData tagged actions and on this way maybe workaround this as a solution, if possible?
#anthino
Is there a way to reroute odata to api, make /odata prefix as /api and make it work?
if you add 'opt.AddRouteComponents("api", GetEdmModel())', remember to remove
[Route("api/[controller]")] and other attribute routings
Should I make another controller that will store all OData tagged actions and on this way maybe workaround this as a solution, if possible?
Basically, it's better to create two controllers, one for odata, the other for others. In your scenario, you mixed them together. You should be careful about this. You can use 'app.UseODataRouteDebug()' middleware to help you debug.
I think your ProjectController should be inheriting from ODataController not ControllerBase. With the ODataController, you should get the context url and the #odata.count

What is the entry point for adding code to Blazor?

I need to add some code to a Blazor WASM app that run as the application is starting up. I want to make a call to an API to get some settings to use during the rest of the application's lifetime.
I have verified that the API is configured correctly and that it returns data.
I've tried adding both MainLayout.razor.cs as well as App.razor.cs in order to make the call.
Neither of these worked. However when I add the SAME code to one of my other components (below), it works fine.
public class ViewMenuModel : ComponentBase
{
[Inject] HttpClient Http { get; set; }
[Inject] AppState AppState { get; set; }
protected override async Task OnInitializedAsync()
{
Settings = await Http.GetJsonAsync<List<Settings>>("settings");
UpdateSettings(Settings);
}
protected void UpdateSettings(List<Settings> settings)
{
AppState.SetSettings(settings);
}
}
Is it possible that I'm just missing something? Is this kind of thing supposed to work from either MainLayout or App?? If so, what's the trick?
It's been some time since I asked this question initially, but I think it might be valuable for future people....
When I started, I think we were on .Net core 3.1, since then, migrating to .net 6, there's actual Microsoft documentation on how to add these types of configurations
https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/configuration?view=aspnetcore-6.0
In Program.cs
var http = new HttpClient()
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};
builder.Services.AddScoped(sp => http);
using var response = await http.GetAsync("cars.json");
using var stream = await response.Content.ReadAsStreamAsync();
builder.Configuration.AddJsonStream(stream);

How to get base uri of site on application start?

I have asp.net mvc application. I was using the following code to get base URI inside controller.
var baseApplicationUrl = string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority);
this was working fine.
Is there a way to construct baseuri in Application_Start()?
In application start method HttpContext.Current.Request will throw exception
Update 1
I have a UISettings class which holds some links. The base URI of these links would be different based on how the application is hosted. For example it could be http://www.example.com/home/index or https://www.example.com/home/index or http://www.example.com/subdomain/home/index. so home/index has different base uri depends on how its hosted.
I understand that the request is not available on application start. But i wanted to load the UISettings on application start so i can register with DI framework as singleton instance.
public class UISettings
{
public string Link1 {get;set;}
public string Link2 {get;set;}
public static UiSettings Load()
{
// need to get baseURI here???
var settings = new UISettings();
settings.Link1 = baseURI + "/home/index";
return settings;
}
}
and then register with DI framework as singleton on application start so i can inject it in any class latter
container.RegisterInstance<UISettings>(UiSettings.Load());
I'm not sure what you are trying to accomplish here. At Application_Start() stage Request is not constructed yet, so I think the earliest time you can get URI from Current Request is in Aplication_BeginRequest(), before then it is your starting page that can be assigned in and used from WebConfig file.

In ASP.NET 5, how do I get the chosen route in middleware?

I am building an ASP.NET 5 (vNext) site that will host dynamic pages, static content, and a REST Web API. I have found examples of how to create middleware using the new ASP.NET way of doing things but I hit a snag.
I am trying write my own authentication middleware. I would like to create a custom attribute to attach to the controller actions (or whole controllers) that specifies that it requires authentication. Then during a request, in my middleware, I would like to cross reference the list of actions that require authentication with the action that applies to this current request. It is my understanding that I configure my middleware before the MVC middleware so that it is called first in the pipeline. I need to do this so the authentication is done before the request is handled by the MVC controller so that I can't prevent the controller from ever being called if necessary. But doesn't this also mean that the MVC router hasn't determined my route yet? It appears to me the determination of the route and the execution of that routes action happen at one step in the pipeline right?
If I want to be able to determine if a request matches a controller's action in a middleware pipeline step that happens before the request is handled by the controller, am I going to have to write my own url parser to figure that out? Is there some way to get at the routing data for the request before it is actually handled by the controller?
Edit: I'm beginning to think that the RouterMiddleware might be the answer I'm looking for. I'm assuming I can figure out how to have my router pick up the same routes that the standard MVC router is using (I use attribute routing) and have my router (really authenticator) mark the request as not handled when it succeeds authentication so that the default mvc router does the actual request handling. I really don't want to fully implement all of what the MVC middleware is doing. Working on trying to figure it out. RouterMiddleware kind of shows me what I need to do I think.
Edit 2: Here is a template for the middleware in ASP.NET 5
public class TokenAuthentication
{
private readonly RequestDelegate _next;
public TokenAuthentication(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//do stuff here
//let next thing in the pipeline go
await _next(context);
//do exit code
}
}
I ended up looking through the ASP.NET source code (because it is open source now!) and found that I could copy the UseMvc extension method from this class and swap out the default handler for my own.
public static class TokenAuthenticationExtensions
{
public static IApplicationBuilder UseTokenAuthentication(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder
{
DefaultHandler = new TokenRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
}
Then you create your own version of this class. In my case I don't actually want to invoke the actions. I will let the typical Mvc middleware do that. Since that is the case I gut all the related code and kept just what I needed to get the route data which is in actionDescriptor variable. I probably can remove the code dealing with backing up the route data since I dont think what I will be doing will affect the data, but I have kept it in the example. This is the skeleton of what I will start with based on the mvc route handler.
public class TokenRouteHandler : IRouter
{
private IActionSelector _actionSelector;
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
EnsureServices(context.Context);
context.IsBound = _actionSelector.HasValidAction(context);
return null;
}
public async Task RouteAsync(RouteContext context)
{
var services = context.HttpContext.RequestServices;
EnsureServices(context.HttpContext);
var actionDescriptor = await _actionSelector.SelectAsync(context);
if (actionDescriptor == null)
{
return;
}
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
if (actionDescriptor.RouteValueDefaults != null)
{
foreach (var kvp in actionDescriptor.RouteValueDefaults)
{
if (!newRouteData.Values.ContainsKey(kvp.Key))
{
newRouteData.Values.Add(kvp.Key, kvp.Value);
}
}
}
try
{
context.RouteData = newRouteData;
//Authentication code will go here <-----------
var authenticated = true;
if (!authenticated)
{
context.IsHandled = true;
}
}
finally
{
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
private void EnsureServices(HttpContext context)
{
if (_actionSelector == null)
{
_actionSelector = context.RequestServices.GetRequiredService<IActionSelector>();
}
}
}
And finally, in the Startup.cs file's Configure method at the end of the pipeline I have it setup so that I use the same routing setup (I use attribute routing) for the both my token authentication and mvc router.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//Other middleware delcartions here <----------------
Action<IRouteBuilder> routeBuilder = routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
app.UseTokenAuthentication(routeBuilder);
//Middleware after this point will be blocked if authentication fails by having the TokenRouteHandler setting context.IsHandled to true
app.UseMvc(routeBuilder);
}
Edit 1:
I should also note that at the moment I am not concerned about the extra time required to select the route twice which is what I think would happen here since both my middleware and the Mvc middleware will be doing that. If that becomes a performance problem then I will build the mvc and authentication in to one handler. That would be best idea performance-wise, but what I have shown here is the most modular approach I think.
Edit 2:
In the end to get the information I needed I had to cast the ActionDescriptor to a ControllerActionDescriptor. I am not sure what other types of actions you can have in ASP.NET but I am pretty sure all my action descriptors should be ControllerActionDescriptors. Maybe the old legacy Web Api stuff needs another type of ActionDescriptor.

Using SDL Tridion 2011 Core Service to create Components programatically

I have seen some of the questions/answers related to this topic here, however still I am not getting the suggestion which I want. So I am posting my question again here, and I would be thankful for your valuable time and answers.
I would like to create “Component, Page, SG, Publication, Folders “ via programmatically in SDL Tridion Content Manager, and later on, I would like to add programmatically created components in Page and attach CT,PT for that page, and finally would like to publish the page programmatically.
I have done these all the activities in SDL Tridion 2009 using TOM API (Interop DLL's), and I tried these activities in SDL Tridion 2011 using TOM.Net API. It was not working and later on I came to know that, TOM.Net API will not support these kinds of works and it is specifically only for Templates and Event System. And finally I came to know I have to go for Core services to do these kinds of stuffs.
My Questions:
When I create console application to create component programmatically using core service, what are the DLL’s I have to add as reference?
Earlier, I have created the exe and ran in the TCM server, the exe created all the stuffs, can I used the same approach using core services too? Will it work?
Is BC still available or Core Service replaced BC? (BC-Business Connector)
Can anyone send some code snippet to create Component/Page (complete class file will be helpful to understand better)
You will only need to reference Tridion.ContentManager.CoreService.Client.dll. You may want to reference Tridion.Common.dll to get access to some helpful classes such as TcmUri, but it is not needed.
You client program will make an explicit connection with the core service on a machine that you specify. If done properly, you can run the client both on the same machine as the Tridion Content Manager or on a different machine.
The Business Connector is still available, but has been superseded by the Core Service.
Have a look at these links:
Updating Components using the Core Service in SDL Tridion 2011
In SDL Tridion 2011, how can I process metadata on an item using the Core Service?
And the standard documentation on the topic connecting to the Core Service from .NET.
If you need more help with the code, I suggest you show us the code you've already written and explain what isn't working.
I will try to answer your questions:
You have to reference Tridion.ContentManager.CoreService.Client and add some stuff to app.config. It's described here
It will work from CM server, as well as from any other machine, provided it can access CoreService
CoreService is replacement for BC. BC is deprecated and will be dropped soon
You will get all the basic info from here.
This should be enough for you to start. If you will have specific problems - post them as a seperate questions.
From How can i use engine object in my console application
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.

Resources