Context
Given the following route
config.Routes.MapHttpRoute(
name: "FooBarBazRoute",
routeTemplate: "foo-bar-baz/{id}",
defaults: new
{
controller = "FooBarBaz",
id = RouteParameter.Optional
});
and by using Hyprlinkr
var linker = new RouteLinker(this.Request);
var id = "813bafcc-8329-<trimmed>"
var href = this
.linker
.GetUri<FooBarBazController>(
c => c.Get(id))
.ToString()
the value of href looks like this:
"http://d59503db-1e96-<trimmed>/foobarbaz/813bafcc-8329-<trimmed>"
Question
Is it possible to customize Hyprlinkr so that href looks like:
"http://d59503db-1e96-<trimmed>/foo-bar-baz/813bafcc-8329-<trimmed>"
That is, instead of foobarbaz, it'd be nice if Hyprlinkr creates foo-bar-baz – similar to the route template, which is foo-bar-baz/{id}.
Yes, this is possible. That's one of the main reasons for the existence of the IRouteDispatcher interface in Hyprlinkr.
Define a custom implementation of IRouteDispatcher, like this:
public class MyDispatcher : IRouteDispatcher
{
private readonly IRouteDispatcher dispatcher;
public MyDispatcher(IRouteDispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
public Rouple Dispatch(
MethodCallExpression method,
IDictionary<string, object> routeValues)
{
if (method.Method.ReflectedType == typeof(FooBarBazController))
return new Rouple("FooBarBazRoute", routeValues);
return this.dispatcher.Dispatch(method, routeValues);
}
}
Use this to create your RouteLinker instance:
var linker = new RouteLinker(
request,
new MyDispatcher(new DefaultRouteDispatcher()));
You can add more special cases to MyDispatcher.Dispatch if you have more named routes to which you need to dispatch.
Related
I created my own Entity called "MyProductEntity" (and also Definition/Collection) which has a relation to some Product (eg. a product is linked with another product)
The problem is that MyProductEntity->connectedProduct doesn't contain prices (eg. calculatedPrices). Does anyone know how to load the prices? I read somewhere that I should use SalesChannelProductEntity instead of ProductEntity? But I am not sure how? Any hints?
Here are (I hope) the relevant parts from my code:
class MyProductEntity extends Entity
{
protected string $productId;
protected string $productVersionId;
protected ProductEntity $product;
protected string $connectedProductId;
protected string $connectedProductVersionId;
protected ProductEntity $connectedProduct;
//I tried to use here "SalesChannelProductEntity" instead of "ProductEntity"
//...
//other properties
}
in some subscriber: (eg.: for ProductPageLoadedEvent::class) I load MyProductEntity like this:
public function getMyProducts(ProductPageLoadedEvent $event): array
{
$page = $event->getPage();
$salesChannelContext = $event->getSalesChannelContext();
$product = $page->getProduct();
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $product->getId()));
$criteria->addAssociation('connectedProduct');
$myProducts = $this->myProductRepository->search($criteria, $salesChannelContext);
//$this->myProductRepository = defined via services.xml / in constructor:
// EntityRepository $myProductRepository
}
As you already found out the field calculatedPrices and similar are exclusive to the SalesChannelProductDefinition and SalesChannelProductEntity. You could simply create your own sales channel definition in addition to the regular definition:
class SalesChannelMyProductDefinition extends MyProductDefinition implements SalesChannelDefinitionInterface
<service id="Foo\MyPlugin\SalesChannel\SalesChannelMyProductDefinition">
<tag name="shopware.sales_channel.entity.definition"/>
</service>
Then use the repository of your new sales channel definition:
$myProduct = $this->getContainer()->get('sales_channel.my_product.repository')
->search($criteria, $salesChannelContext)->first();
// should then be an instance of SalesChannelProductEntity
$salesChannelProduct = $myproduct->getConnectedProduct();
I created a wrapper around DryIoC container which is just a class that delegates tasks to DryIoC methods ( for example, myContainer.Register<T>() will call dryIoC.Register<T>() ). the goal is just to hide the real implementation behind my interface so I can switch to another DI container if I wanted to.
All works fine, but I faced a problem today when I tried to work with Scopes likes in this example code taken from here
// example using DryIoC
var container = new Container();
container.Register<B>();
using (var scope = container.OpenScope())
{
var a = new A();
scope.UseInstance(a); // Scoped
scope.Resolve<B>(); // will inject `a`
}
var anotherA = new A();
container.UseInstance(anotherA); // Singleton
container.Resolve<B>(); // will inject `anotherA`
my naive wrapper implementation was to create another constructor that accepts an instance of DryIoC container and do it like this :
// My Wrapper class
public Infrastructure.IMyContainer OpenScope()
{
return new MyContainer(dryIoC.OpenScope());
}
my understanding is that dryIoC.OpenScope() returns a new instance of the container, so all I had to do is save this instance internally and use it to resolve my classes. but that implementation didn't work for me, here my unit test :
[Test]
public void OpenScope_Creates_A_Scoped_Container()
{
var _container = new MyContainer();
_container.Register<IMyInterface, MyImpl>();
_container.Register<MyDependingClass>();
MyDependingClass cls1 = null;
MyDependingClass cls2 = null;
var dep = new MyImpl();
using (var scope = _container.OpenScope())
{
scope.UseInstance(dep);
cls1 = scope.Resolve<MyDependingClass>(); // this should inject 'dep' instance created in the line before the creation of the scope
}
cls2 = _container.Resolve<MyDependingClass>(); // this should inject another instance.
cls1.Dep.ShouldBeSameAs(dep); // cls1 was resolved in the scope, so it should get 'dep' instance
cls1.Dep.ShouldNotBeSameAs(cls2.Dep); // cls2.Dep should be different
}
// stub classes/interfaces
class MyDependingClass
{
public MyDependingClass(IMyInterface dep)
{
Dep = dep;
}
public IMyInterface Dep { get; }
}
class MyImpl : IMyInterface { }
but this test fails at cls1.Dep.ShouldBeSameAs(dep); telling me that the two instances of IMyInterface was different !!!
Am I missing something ??
Currently I have two controllers
1 - Parent Controller
2 - Child Controller
I access my Parent Controller like this
someurl\parentcontroller
Now I want to access my children controller like this
someurl\parentcontroller\1\childcontroller
This last url should return all the children of a particular parent.
I have this route currently in my global.asax file
routes.MapHttpRoute ("Route1", "{controller}/{id}", new { id = RouteParameter.Optional });
I am not sure how can I achieve my parent\id\child hierarchy.. How should I configure my routes to achieve this? Ideas?
Configure the routes as below. The {param} is optional (use if you need):
routes.MapHttpRoute(
name: "childapi",
routeTemplate: "api/Parent/{id}/Child/{param}",
defaults: new { controller = "Child", param = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then call the child APi as /api/Parent/1/child
The parent can be called simple as /api/Parent/
The child controller:
public class ChildController : ApiController
{
public string Get(int id)
{
//the id is id between parent/{id}/child
return "value";
}
.......
}
Since Web API 2 you can now use Route Attributes to define custom routing per Method,
[Route("api/customers/{id:guid}/orders")]
public IEnumerable<Order> GetCustomerOrders(Guid id) {
return new Order[0];
}
You also need to add following line to WebApiConfig.Register() initialization method,
config.MapHttpAttributeRoutes();
Full article,
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
I wanted to handle this in a more general way, instead of wiring up a ChildController directly with controller = "Child", as Abhijit Kadam did. I have several child controllers and didn't want to have to map a specific route for each one, with controller = "ChildX" and controller = "ChildY" over and over.
My WebApiConfig looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ChildApi",
routeTemplate: "api/{parentController}/{parentId}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
My parent controllers are very standard, and match the default route above. A sample child controller looks like this:
public class CommentController : ApiController
{
// GET api/product/5/comment
public string Get(ParentController parentController, string parentId)
{
return "This is the comment controller with parent of "
+ parentId + ", which is a " + parentController.ToString();
}
// GET api/product/5/comment/122
public string Get(ParentController parentController, string parentId,
string id)
{
return "You are looking for comment " + id + " under parent "
+ parentId + ", which is a "
+ parentController.ToString();
}
}
public enum ParentController
{
Product
}
Some drawbacks of my implementation
As you can see, I used an enum, so I'm still having to manage parent controllers in two separate places. It could have just as easily been a string parameter, but I wanted to prevent api/crazy-non-existent-parent/5/comment/122 from working.
There's probably a way to use reflection or something to do this on the fly without managing it separetly, but this works for me for now.
It doesn't support children of children.
There's probably a better solution that's even more general, but like I said, this works for me.
An option beyond using default mvc routing is to look at Attribute Routing - https://github.com/mccalltd/AttributeRouting. Although its more work, decorating individual action methods provides a ton of flexibility when you need to design complicated routes. You can also use it in conjunction with standard MVC routing.
I've successfully used the AccountManagement code to retrieve basic AD information but it's only returning a very limited set of information about the returned object. How can I get extended information from AD using the AccountManagement functionality. Specifically the Job Title or title as it seems to be called in my instance of AD.
I know how to do it using the older DirectoryServices but I'd like to know how to do it using the new namespace.
Yes, the default set of properties on UserPrincipal is quite limited - but the great part is: there's a neat extensibility story in place!
You need to define a class descending from UserPrincipal and then you can very easily get access to a lot more properties, if needed.
The skeleton would look something like this:
namespace ADExtended
{
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("User")]
public class UserPrincipalEx : UserPrincipal
{
// Inplement the constructor using the base class constructor.
public UserPrincipalEx(PrincipalContext context) : base(context)
{ }
// Implement the constructor with initialization parameters.
public UserPrincipalEx(PrincipalContext context,
string samAccountName,
string password,
bool enabled) : base(context, samAccountName, password, enabled)
{}
UserPrincipalExSearchFilter searchFilter;
new public UserPrincipalExSearchFilter AdvancedSearchFilter
{
get
{
if (null == searchFilter)
searchFilter = new UserPrincipalExSearchFilter(this);
return searchFilter;
}
}
// Create the "Title" property.
[DirectoryProperty("title")]
public string Title
{
get
{
if (ExtensionGet("title").Length != 1)
return string.Empty;
return (string)ExtensionGet("title")[0];
}
set { ExtensionSet("title", value); }
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityValue);
}
// Implement the overloaded search method FindByIdentity.
public static new UserPrincipalEx FindByIdentity(PrincipalContext context, IdentityType identityType, string identityValue)
{
return (UserPrincipalEx)FindByIdentityWithType(context, typeof(UserPrincipalEx), identityType, identityValue);
}
}
}
And that's really almost all there is! The ExtensionGet and ExtensionSet methods allow you to "reach down" into the underlying directory entry and grab out all the attributes you might be interested in....
Now, in your code, use your new UserPrincipalEx class instead of UserPrincipal:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// Search the directory for the new object.
UserPrincipalEx myUser = UserPrincipalEx.FindByIdentity(ctx, "someUserName");
if(myUser != null)
{
// get the title which is now available on your "myUser" object!
string title = myUser.Title;
}
}
Read all about the System.DirectoryServices.AccountManagement namespace and its extensibility story here:
Managing Directory Security Principals in the .NET Framework 3.5
Update: sorry - here's the UserPrincipalExSearchFilter class - missed that one in the original post. It just shows the ability to also extend the search filters, if need be:
public class UserPrincipalExSearchFilter : AdvancedFilters
{
public UserPrincipalExSearchFilter(Principal p) : base(p) { }
public void LogonCount(int value, MatchType mt)
{
this.AdvancedFilterSet("LogonCount", value, typeof(int), mt);
}
}
To Augment the above I have knocked up an extension method to call ExtensionGet. It uses reflection to get hold of the protected method you would otherwise have to inherit. You might need to use this if you are returning UserPrincipalObjects from Groups.Members, for example
public static class AccountManagmentExtensions
{
public static string ExtensionGet(this UserPrincipal up, string key)
{
string value = null;
MethodInfo mi = up.GetType()
.GetMethod("ExtensionGet", BindingFlags.NonPublic | BindingFlags.Instance);
Func<UserPrincipal, string, object[]> extensionGet = (k,v) =>
((object[])mi.Invoke(k, new object[] { v }));
if (extensionGet(up,key).Length > 0)
{
value = (string)extensionGet(up, key)[0];
}
return value;
}
}
There are simpler ways of getting to that info. Here is the way I got to Job Title in VB.NET:
Dim yourDomain As New PrincipalContext(ContextType.Domain, "yourcompany.local")
Dim user1 As UserPrincipal = UserPrincipal.FindByIdentity(yourDomain, principal.Identity.Name)
Dim Entry As DirectoryServices.DirectoryEntry = user1.GetUnderlyingObject()
Dim JobTitle As String = Entry.Properties.Item("Title").Value.ToString
To expand on Programmierus' comment, here is a simple way to do this on the fly in C#.
public static string GetProperty(UserPrincipal userPrincipal, string property)
{
DirectoryEntry d = (DirectoryEntry)userPrincipal.GetUnderlyingObject();
return d.Properties[property]?.Value?.ToString();
}
I have extension method that does something like this
public static void DoStuff(this ObjectContext context)
{
using(var newContext = new MyEntitiesContext())
{
// do stuff
newContext.SaveChanges();
}
context.SaveChanges();
}
I was wondering if there a way to new a context of the same type as the context passed in instead of specifying MyEntitiesContext?
Thanks in advance
If you don't mind reflection:
var context = Activator.CreateInstance(context.GetType());
Now you either need a base Type or - if you still don't mind reflection - you can simply call the method by name. Or, since you are using C#4 you could go with dynamic.
Edit: You could also Go this way:
public static void DoStuff<T>(this T context) where T : ObjectContext, new()
{
using(var newContext = new T())
{
// do stuff
newContext.SaveChanges();
}
context.SaveChanges();
}