How do I generate a url inside a c# service? - c#

Really simple I hope. I just want to do the equivalent of
Url.Action("SomeAction", "SomeController", new { id = 3 });
But inside a service class. Not inside a controller class or IActionResult method
In a plain old service class. Because of the service call having all the data I don't want to pass in other information so my service call is nice and clean.
I've come close but nothing seems to work, either that or it cant be done.
I tried to dependency inject this
services.AddScoped<IUrlHelper>(x => x
.GetRequiredService<IUrlHelperFactory>()
.GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));
In my service call I used (DI) this
public AdminService(..., IUrlHelper urlHelper)
so in my service method I could to this
string editUrl = _urlHelper.Action("EditRole", "Admin", new { id = 0 });
which got rid of all the red squiglies but at run time this bit caused me a problem
.GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext));

You can inject IUrlHelper interface inside a service class.
public class ServiceClass
{
private readonly IActionContextAccessor _actionContextAccessor;
private readonly IUrlHelperFactory _urlHelperFactory;
public ServiceClass(IActionContextAccessor actionContextAccessor,
IUrlHelperFactory urlHelperFactory,)
{
_actionContextAccessor = actionContextAccessor;
_urlHelperFactory = urlHelperFactory;
}
public string CreateUrl()
{
var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext);
string url = urlHelper.Action("SomeAction", "SomeController");
return url;
}
}

#SMM I had to add this to my startup but otherwise, works, so thank you
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IUrlHelper, UrlHelper>();

Url.Action generates only an url.
#Url.Action("actionName", "controllerName", new { id = id })
Html.ActionLink generates an tag automatically.

Related

Generate the code for Blazor services based on controllers?

I'm writing a Blazor webassembly application, and I would like to rationalize the code in the client.
In the server part, there are controllers. In the client part, there are services calling for the controllers. Usually, one service calls only one controller. I'm not comfortable with the code of the client services, as IMO it is redundant with the code of the server controller. See example below.
Server controller sample
[Route("api/[controller]/{Lodge}/[action]")]
[ApiController]
public class CatalogController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
public CatalogController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[Authorize]
[HttpGet]
public Item[] GetAllItems(string Lodge)
{
Lodge = HttpUtility.UrlDecode(Lodge);
return new Item[]
{
// Get the data from a database
new Item() { Name = string.Format("{0} 1", Lodge) },
new Item() { Name = string.Format("{0} 2", Lodge) },
new Item() { Name = string.Format("{0} 3", Lodge) }
};
}
}
Client service sample
public interface ICatalogService
{
Task<Item[]> AllItems(string Lodge);
}
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Item[]> AllItems(string Lodge)
{
return await _httpClient.GetFromJsonAsync<Item[]>(string.Format("api/catalog/{0}/getallitems", HttpUtility.UrlEncode(Lodge)));
}
}
I am not happy with the the code for method AllItems in the service because
Redundant with the definition of the controller. The return type and the parameters could be infered based on controller definition.
Error prone as I have to build the URL for the controller "by-hand". But it is already defined in the controller route.
Error prone again, as I use a generic method GetFromJsonAsync, so I guess there is a deserialize operation somewhere in it. If there is a mismatch between controller return type and service call, I'd prefer a compile error instead of a runtime error.
Actually, I would be very happy NOT writing the code for the service, as it seems all the needed info are already present in the controller definition. Is there any coding scheme, or framework, or code-generator tool to do that for me and solve the all 3 issues above ?

How to register api controller from a library with configuration

What I have done is created a small API in a class library. This API would be used by other sites. Think of it as a standard endpoint that all of our websites will contain.
[Route("api/[controller]")]
[ApiController]
public class CustomController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
The above is in a class library. Now what i would like to do is be able to add this to the projects in a simple manner.
app.UseCustomAPI("/api/crap");
I am not exactly sure how i should handle routing to the api controllers in the library. I created a CustomAPIMiddleware which is able to catch that i called "/api/crap" however i am not sure how i should forward the request over to CustomController in the library
public async Task Invoke(HttpContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
PathString matched;
PathString remaining;
if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matched, out remaining))
{
PathString path = context.Request.Path;
PathString pathBase = context.Request.PathBase;
context.Request.PathBase = pathBase.Add(matched);
context.Request.Path = remaining;
try
{
await this._options.Branch(context);
}
finally
{
context.Request.PathBase = pathBase;
context.Request.Path = path;
}
path = new PathString();
pathBase = new PathString();
}
else
await this._next(context);
}
After having done that i am starting to think i may have approached this in the wrong manner and should actually be trying to add it directly to the routing tables somehow. That being said i would like it if they could customize the endpoint that the custom controller reads from.
Update
The following does work. Loading and registering API Controllers From Class Library in ASP.NET core
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
However i am really looking for a middlewere type solution so that users can simply add it and i can configure the default settings or they can change some of the settings. The above example would not allow for altering the settings.
app.UseCustomAPI("/api/crap");
Update from comment without Assembly
If i dont add the .AddApplicationPart(Assembly.Load(new AssemblyName("WebAPI")));
This localhost page can’t be found No webpage was found for the web address:
https://localhost:44368/api/Custom
To customise the routing for a controller at runtime, you can use an Application Model Convention. This can be achieved with a custom implementation of IControllerModelConvention:
public class CustomControllerConvention : IControllerModelConvention
{
private readonly string newEndpoint;
public CustomControllerConvention(string newEndpoint)
{
this.newEndpoint = newEndpoint;
}
public void Apply(ControllerModel controllerModel)
{
if (controllerModel.ControllerType.AsType() != typeof(CustomController))
return;
foreach (var selectorModel in controllerModel.Selectors)
selectorModel.AttributeRouteModel.Template = newEndpoint;
}
}
This example just replaces the existing template (api/[controller]) with whatever is provided in the CustomControllerConvention constructor. The next step is to register this new convention, which can be done via the call to AddMvc. Here's an example of how that works:
services.AddMvc(o =>
{
o.Conventions.Add(new CustomControllerConvention("api/whatever"));
});
That's all that's needed to make things work here, but as you're offering this up from another assembly, I'd suggest an extension method based approach. Here's an example of that:
public static class MvcBuilderExtensions
{
public static IMvcBuilder SetCustomControllerRoute(
this IMvcBuilder mvcBuilder, string newEndpoint)
{
return mvcBuilder.AddMvcOptions(o =>
{
o.Conventions.Add(new CustomControllerConvention(newEndpoint));
});
}
}
Here's how that would be called:
services.AddMvc()
.SetCustomControllerRoute("api/whatever");
This whole approach means that without a call to SetCustomControllerRoute, api/Custom will still be used as a default.

ASP.NET MVC - unit testing creation of action method URL's in a business assembly

I'm trying to write some unit tests for my ASP.NET MVC code, but I'm hitting a road block.
I have entity partial classes (in a business class library assembly) that need to determine a URL to call for an action method and controller. In order to do that, I found this snippet of code which works nicely - but alas, this uses the HttpContext.Current and thus prevents me from writing any unit tests:
public string NavigateUrl
{
get
{
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
UrlHelper urlHelper = new UrlHelper(new RequestContext(httpContextWrapper, RouteTable.Routes.GetRouteData(httpContextWrapper)));
string url = urlHelper.Action("SomeAction", "MyController");
}
}
I am reading about the HttpContextBase - but how does this come into play here?? Or is there another way to determine an action URL inside an entity class (that is in a business assembly - NOT the MVC project and not inside a controller or other MVC infrastructure class)?
Update: I need to return this URL from an entity class as a string, since I need to use it in a grid as the navigation URL of a hyperlink. And in reality, there are numerous conditions being checked and the URL string returned can be one of several possibilities - so I cannot just replace it by a single controller call...
Create an abstraction to represent the desired functionality.
For example
public interface IUrlHelper {
string Action(string actionName, string controllerName);
//TODO: create other desired members to be exposed
}
You then create a factory for that abstraction. Since you are not injecting it into the entities we are using a service locator ani-pattern.
public static class UrlHelperFactory {
public static Func<IUrlHelper> Create = () => {
throw new NotImplementedException();
};
}
The helper and factory are not coupled to anything and could live anywhere in the solution.
The following test mocks the service to allow the entity to be tested in isolation.
[TestClass]
public class UrlHelperFactory_Should {
public class MyTestEntity {
public string NavigateUrl {
get {
var urlHelper = UrlHelperFactory.Create();
string url = urlHelper.Action("SomeAction", "MyController");
return url;
}
}
}
[TestMethod]
public void Generate_NavigationUrl() {
//Arrange
var mockHelper = Mock.Of<IUrlHelper>();
UrlHelperFactory.Create = () => {
return mockHelper;
};
var expected = "http://my_fake_url";
Mock.Get(mockHelper)
.Setup(_ => _.Action(It.IsAny<string>(), It.IsAny<string>()))
.Returns(expected);
var sut = new MyTestEntity();
//Act
var actual = sut.NavigateUrl;
//Assert
actual.Should().NotBeNullOrWhiteSpace()
.And.Be(expected);
}
}
In production code at the composition root you make sure that the factory knows how to build the service
UrlHelperFactory.Create = () => {
var httpContextWrapper = new HttpContextWrapper(HttpContext.Current);
var urlHelper = new UrlHelper(new RequestContext(httpContextWrapper, RouteTable.Routes.GetRouteData(httpContextWrapper)));
return new DefaultUrlHelperWrapper(urlHelper);
};
Where a wrapper could look like this...
internal class DefaultUrlHelperWrapper : IUrlHelper {
private UrlHelper urlHelper;
public DefaultUrlHelperWrapper(UrlHelper urlHelper) {
this.urlHelper = urlHelper;
}
public string Action(string actionName, string controllerName) {
return urlHelper.Action(actionName, controllerName);
}
//TODO: Implement other members
}

Castle Windsor - Pass resolve arguments to the inferior layers

I have a web api project where controllers depend on some storage layer. E.g.
each controller has similar code:
public class ResourceController : ApiController
{
private readonly IResourceStorage _resourceStorage;
private readonly IOtherStorage _otherStorage;
public ResourceController(IResourceStorage resourceStorage, IOtherStorage otherStorage)
{
_resourceStorage = resourceStorage;
_otherStorage = otherStorage;
}
// Web API Actions
}
A common code for storage looks like this:
public class ResourceStorage : DBProvider, IResourceStorage
{
public ResourceStorage(string connectionString) : base(connectionString)
{
}
// storage methods
}
Based on some specific condition of the Web Api request, I need to be able to inject different connectionStrings to Storages of controller. Pseudo-code could look like that:
public class WindsorControllerActivator : IHttpControllerActivator
{
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
string connectionString = ChooseDbConnectionStringBasedOnRequestContext(request);
var controller =
(IHttpController)_container.Resolve(connectionString, controllerType, new {databaseType = connectionString});
request.RegisterForDispose(
new Release(
() => _container.Release(controller)));
return controller;
}
}
I do not know how to pass the parameter to the storage in the less destructive way:)
What have I tried? Alternatives:
Pass parameterers as Castle Windsor Additional Arguments and treat it in next layer using DynamicParameters. The arguments get the controller layer but I could not find a way to get them to the storage - it has its own CreationContext, and I cannot find a way to pass it on from controller to storage.
Have N(equal to number of connection strings) containers and choose one of those inside ControllerActivator. Seems a huge and ugly solution, totally non-flexible but it works.
Create N sets of Controllers each with their own name and inside of storage DynamicParameters check Handler's component name and choose the connection string. From the ControllerActivator pass in the key to the correct set of Controllers. Also ugly - too many registrations of controllers and a lot of plumbing code.
You could use a factory pattern:
public interface IResourceStorageFactory
{
IResourceStorage Create(int numberOfResources);
}
public class ResourceStorageFactory : IResourceStorageFactory
{
public IResourceStorage Create(HttpRequestMessage request)
{
var connectionString = ChooseDbConnectionStringBasedOnRequestContext(request);
return new ResourceStorage(connectionString);
}
}
and then simply
private readonly IResourceStorage _resourceStorage;
private readonly IOtherStorage _otherStorage;
public ResourceController(IResourceStorageFactory resourceStorageFactory, IOtherStorage otherStorage)
{
_resourceStorage = resourceStorageFactory.Create(Request);
_otherStorage = otherStorage;
}
The solution I found is introducing a connection string provider:
public interface IConnectionStringProvider
{
string ConnectionString { get; }
}
When register this provider as per web request with a factory method:
kernel.Register(
Component.For(typeof (IConnectionStringProvider))
.ImplementedBy(typeof (ConnectionStringProvider))
.UsingFactoryMethod(
(k, context) =>
new ConnectionStringProvider(context.AdditionalArguments["connectionString"].ToString()))
.LifestylePerWebRequest());
And inside the controller activator first resolve the connection string provider with the right parameters and then the controller:
// The lifecycle of connection string provider if per web request.
// We resolve it first time with the correct parameters,
// so it is injected with the correct connection string throught the lifecycle of the request.
_container.Resolve<IConnectionStringProvider>(new {connectionString});
var controller =
(IHttpController) _container.Resolve(controllerType);
It still not perfect and looks kind of hackish but it allows to keep dependencies on the interfaces of the inferior layer and requires less changes of the codebase from the solutions I've found

C# unit testing API 2 call

I have a web api 2 web service get method. Inside I'm using HttpContext.Current.Request.UserHostAddress. When calling my controller method directly int he unit test this isn't filled in so is errors with null object. So I searched for how to fill this in and found the following which helped with that issue: Add IP address to HttpRequestMessage
However, this needs a server name to send the request to. The problem is that when tests run the VSExpress will need to be running for this API web service, which it won't be when just running the tests. On top of that even if it was it seems it picks a random port to run on so I couldn't hardcode the address like he does in the above link. How can I test my api 2 method given the above issues?
This is the line that blows up when I just test the api method
string ip = HttpContext.Current.Request.UserHostAddress;
[EDIT] Answer
Just so everyone knows here is the solution in code
public class MyController : ApiController
{
private: HttpRequestBase httpRequest;
public MyController()
{
httpRequest = new HttpRequestWrapper(HttpContext.Current.Request)
}
public MyController(HttpRequestBase http)
{
httpRequest = http;
}
public HttpResponseMessage Get()
{
string ip = httpRequest.UserHostAddress;
}
}
I use Moq in the unit test:
Mock<HttpRequestBase> httpRequestMock = new Mock<HttpRequestBase>();
httpRequestMock.Setup(x => x.UserHostAddress).Returns("127.0.0.1");
// then pass httpRequestMock.Object to my controller ctor and good to go
Decouple your controller from the HTTP context. There might be some built-in functionality to do this with which I'm unfamiliar, but one approach would be to simply inject a mockable object. Consider something like this:
public interface IRequestInformation
{
string UserHostAddress { get; }
}
public class RequestInformation : IRequestInformation
{
public string UserHostAddress
{
get { return HttpContext.Current.Request.UserHostAddress; }
}
}
Now you've abstracted the dependency on HttpContext behind an interface. If you're using dependency injection, inject that interface into your controller. If you're not, you can fake it:
// in your controller...
private IRequestInformation _request;
public IRequestInformation RequestInfo
{
get
{
if (_request == null)
_request = new RequestInformation();
return _request;
}
set { _request = value; }
}
Then use that in your controller logic:
string ip = RequestInfo.UserHostAddress;
Now in your unit tests you can supply a mock/fake/etc. for the RequestInfo property. Either create one manually or use a mocking library. If you create one manually, that's simple enough:
public class RequestInformationFake : IRequestInformation
{
public string UserHostAddress
{
get { return "some known value"; }
}
}
Then just supply that to the controller when arranging the test:
var controller = new YourController();
controller.RequestInformation = new RequestInformationFake();
// run your test
Replace your references to HttpContext by references to HttpContextBase. When in your code, initialize the HttpContextBase with a HttpContextWrapper instance, which is a the default behavior implementation in a web stack.
However in your test inject a custom HttpContextBase implementation where you implement the methods and behaviors needed by your test only.
As precised in the link:
The HttpContextBase class is an abstract class that contains the same
members as the HttpContext class. The HttpContextBase class enables
you to create derived classes that are like the HttpContext class, but
that you can customize and that work outside the ASP.NET pipeline.
When you perform unit testing, you typically use a derived class to
implement members with customized behavior that fulfills the scenario
you are testing.
Add the following method to the controller, or inject the equivalent. It uses the magic string MS_HttpContext because that's what the AspNetWebStack implementation uses for exactly the same purpose.
HttpContextBase HttpContextBase => HttpContext.Current != null
? new HttpContextWrapper(HttpContext.Current)
: (HttpContextBase)Request.Properties["MS_HttpContext"]
Replace all other uses of HttpContext.Current in the controller with HttpContextBase.
When unit testing:
var context = new Mock<HttpContextBase>();
...
controller.Request = new HttpRequestMessage();
controller.Request.Properties["MS_HttpContext"] = context.Object;

Categories

Resources