Assign ajax result to interface inclusive methods - casting

I'm quite new to Typescript and have a bit of a problem to get my code working. I have the following interface/class structure
interface IInterface {
id : number;
method() : string;
}
class IClass implements IInterface {
id : number;
method() : string { return "foo";}
}
Now I want to get some data from a webservice via the following call
$.get("/some/url", (data : Array<IInterface>) => {
for (var i = 0; i < data.length; i++) {
console.log(data[i].id);
console.log(data[i].method());
}
});
While this compiles perfectly fine in typescript and also all properties are set perfectly fine, I get a runtime TypeError data[i].method is not a function
So now my question: How can I cast/assign (?) this correctly, so that the methods are also available in the resulting JavaScript?
UPDATE As requested: a dump of the data I get from the webservice.
data = [{id : 1}, {id : 2}, ...]
Of course, this is a simplified example, the real classes/interfaces have some more properties (they all are assigned correctly), but also only one method (so far, when I get it working, some more will follow).

The problem is what you receive are objects which are complying to the interface, but they don't have a method property inside.
What you need to do in order to get this working is, you need to create new objects from type IClass in the reponse handler which extend the objects inside data:
$.get("/some/url", (data: Array<IInterface>) => {
return data.map((d) => return new IClass(d.id));
});

This is a common problem of converting JSON objects into instances of the classes. There are some proposals discussed here: JSON to TypeScript class instance?
Below is the solution I came up to do such deserialization:
export class Helper
{
public static DESCRIPTOR_SIGN = '$';
private static m_entityModules = [];
private static ReviveDateTime(key: any, value: any): any
{
if (typeof value === 'string')
{
let a = /\/Date\((\d*)\)\//.exec(value);
if (a)
{
return new Date(+a[1]);
}
}
return value;
}
private static RessurectValue(json: any, environments: any[]): any
{
if(json == null)
{
return null;
}
else if(Helper.IsString(json))
{
return json;
}
else if(json instanceof Date)
{
return json;
}
else if(typeof json === 'object')
{
return Helper.RessurectInternal(json, environments);
}
else
{
return json;
}
}
private static RessurectInternal(json: any, environments: any[]): any
{
var instance;
if(!json[Helper.DESCRIPTOR_SIGN])
{
instance = {};
}
else
{
instance = Helper.CreateObject(json[Helper.DESCRIPTOR_SIGN]);
if(Helper.IsUndefined(instance))
{
throw new Error('Unknown type to deserialize:' + json[Helper.DESCRIPTOR_SIGN]);
}
}
for(var prop in json)
{
if(!json.hasOwnProperty(prop) || prop === Helper.DESCRIPTOR_SIGN)
{
continue;
}
let val = json[prop];
instance[prop] = Helper.Ressurect(val, environments);
}
return instance;
}
private static CreateObject(className: string, environments?: any[]): any
{
let instance: any;
for(let e of environments)
{
var construct = e[className];
if(!Helper.IsUndefined(construct))
{
instance = new construct();
break;
}
}
return instance;
}
private static IsNumber(val: any): boolean
{
return typeof val == 'number' || val instanceof Number;
}
private static IsUndefined(val: any): boolean
{
return typeof(val) === 'undefined';
}
private static IsString(val: any): boolean
{
return typeof val == 'string' || val instanceof String;
}
/**
* Deserialize json object object into TypeScript one.
* #param json json object that must have its class name in '$' field
* #param environments list of modules containing all types that can be encountered during deserialization
* #return deserialized typescript object of specified type
*/
public static Ressurect(val: any, environments: any[]): any
{
if(val instanceof Array)
{
if(val.length == 0)
{
return val;
}
else
{
let firstElement = val[0];
if(typeof firstElement !== 'object')
{
return val;
}
else
{
let arr = [];
for (var i = 0; i < val.length; i++)
{
var element = val[i];
arr.push(Helper.RessurectValue(element, environments));
}
return arr;
}
}
}
else
{
return Helper.RessurectValue(val, environments);
}
}
}
Some notes for the above. It works based on the assumption that:
There is a parameterless constructor in every type that can be deserialized.
Every JSON object contain field '$' with its class name inside.
Sample of usage. Lets suppose yu have defined all your serializable classes in one external mode called 'Types'. Then to deserialize JSON object you write:
import * as types from './Types'
//Some code to get jsonObj that has jsonObj.$ set to "RealObject" - a type from "Types" module
let realObj = <types.RealObject>Helper.Resurrect(jsonObj, [types]);
Hope this helps.

Related

How to enable VersionCountDisabler for Glass Mapper in Sitecore for SitecoreQuery and SitecoreChildren attributes

The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work.
In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception.
SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor.
To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.
This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery).
New configuration:
public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
{
}
New attribute:
public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
{
public SitecoreSharedQueryAttribute(string query) : base(query)
{
}
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedQueryConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
New type mapper:
public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
{
public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
: base(parameters)
{
}
public override object MapToProperty(AbstractDataMappingContext mappingContext)
{
var scConfig = Configuration as SitecoreQueryConfiguration;
var scContext = mappingContext as SitecoreDataMappingContext;
using (new VersionCountDisabler())
{
if (scConfig != null && scContext != null)
{
string query = this.ParseQuery(scConfig.Query, scContext.Item);
if (scConfig.PropertyInfo.PropertyType.IsGenericType)
{
Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
if (typeof(IEnumerable<>) == outerType)
{
Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
Func<IEnumerable<Item>> getItems;
if (scConfig.IsRelative)
{
getItems = () =>
{
try
{
return scContext.Item.Axes.SelectItems(query);
}
catch (Exception ex)
{
throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
}
};
}
else
{
getItems = () =>
{
if (scConfig.UseQueryContext)
{
var conQuery = new Query(query);
var queryContext = new QueryContext(scContext.Item.Database.DataManager);
object obj = conQuery.Execute(queryContext);
var contextArray = obj as QueryContext[];
var context = obj as QueryContext;
if (contextArray == null)
contextArray = new[] { context };
return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
}
return scContext.Item.Database.SelectItems(query);
};
}
return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
}
throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
}
{
Item result;
if (scConfig.IsRelative)
{
result = scContext.Item.Axes.SelectSingleItem(query);
}
else
{
result = scContext.Item.Database.SelectSingleItem(query);
}
return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
}
}
}
return null;
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedQueryConfiguration;
}
}
And configure the new type mapper in your glass config (mapper and parameters for the constructor):
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
You can then simply change the SitecoreQuery attribute on your model to SitecoreSharedQuery:
[SitecoreSharedQuery("./*")]
public virtual IEnumerable<YourModel> YourItems { get; set; }
For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren query.
Edit: Added bindings for IEnumerable<ISitecoreQueryParameter> as they are missing and therefor it threw an error.

Unit testing generic htmlHelper methods with nunit

I'm new to nUnit and I've been tasked with creating unit tests for some htmlhelper extension methods.
How should I go about creating a unit test for the following method?
public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, bool orderById, string firstElement = null, object htmlAttributes = null)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items = values.Select(value => new SelectListItem()
{
Text = value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description,
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
IEnumerable<SelectListItem> itemsFiltered = items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();
itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));
return htmlHelper.DropDownListFor(
expression,
itemsFiltered,
firstElement,
htmlAttributes
);
}
Any help would be appreciated
Below is how you write a Unit Test for this. Note that since you have not specified that you use a Mock object framework I'm going to the poor man technique, which is the hand written stubs and mocks. There is also another helper method if you are using Moq.
It is important to note that, in order to simplify the code execution I have made couple of changes to your extension method, so the test would not fail unexpectedly. Checking for any unexpected behaver is a good defensive programming practice anyway.
Back to the tests.
SUT (System Under Test)
This is how the SUT (System Under Test) looks like and supporting types looks like. (Please feel free to modify to your need accordingly)
public static class MyHtmlHelper
{
public static MvcHtmlString EnumDropDownListForOrderBy<TModel, TEnum>
(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TEnum>> expression,
bool orderById, string firstElement = null, object htmlAttributes = null,
Func<ModelMetadata> fromLambFunc = null)
{
ModelMetadata metadata =
ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items =
values.Select(value => new SelectListItem()
{
Text = GetText(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
IEnumerable<SelectListItem> itemsFiltered =
items.Where(e => !string.IsNullOrEmpty(e.Text)).AsEnumerable();
itemsFiltered = itemsFiltered.OrderBy(e => (orderById ? e.Text : e.Value));
return htmlHelper.DropDownListFor
(expression, itemsFiltered, firstElement, htmlAttributes);
}
private static Type GetNonNullableModelType(ModelMetadata metadata) {
return typeof (SomeEnum);
}
private static string GetText<TEnum>(TEnum value) {
return value.GetAttributeFrom<DescriptionAttribute>(value.ToString()) != null
? value.GetAttributeFrom<DescriptionAttribute>(value.ToString()).Description
: string.Empty;
}
}
public static class ExtensionMethodsAttr
{
public static T GetAttributeFrom<T>(this object instance, string propertyName)
where T : Attribute
{
var attrType = typeof(T);
var property = instance.GetType().GetProperty(propertyName);
return property != null ?
(T)property.GetCustomAttributes(attrType, false).First() : default(T) ;
}
}
public enum SomeEnum { A,}
Unit Tests
[TestFixture]
public class HtmlHelperTests
{
[Test]
public void EnumDropDownListForOrderBy_InvokeDropDownListFor_ReturnsExpectedSelectItemResult()
{
//Arrange
var expected = "<select id=\"Foo\" name=\"Foo\"></select>";
var fakeHtmlHelper = CreateHtmlHelperStaticStubs
(new ViewDataDictionary(new FakeViewModel() {Foo = SomeEnum.A}));
//var fakeHtmlHelper = CreateHtmlHelperUsingMoq
(new ViewDataDictionary(new FakeViewModel(){Foo = SomeEnum.A}));
//Act
var result = fakeHtmlHelper.EnumDropDownListForOrderBy
(model => model.Foo, It.IsAny<bool>(), null, null, null);
//Assert
Assert.AreEqual(expected, result.ToString());
}
private static HtmlHelper<FakeViewModel>
CreateHtmlHelperStaticStubs(ViewDataDictionary viewData)
{
var stubControllerContext = new ControllerContext(new FakeHttpContext(), new RouteData(), new FakeController());
var stubViewContext = new ViewContext(stubControllerContext, new FakeView(),
new ViewDataDictionary(new FakeViewModel() { Foo = SomeEnum.A }),
new TempDataDictionary(), new TextMessageWriter());
var fakeViewDataContainer = new FakeViewDataContainer();
fakeViewDataContainer.ViewData = viewData;
return new HtmlHelper<FakeViewModel>(stubViewContext, fakeViewDataContainer);
}
//Moq version
private static HtmlHelper<FakeViewModel>
CreateHtmlHelperUsingMoq(ViewDataDictionary viewData)
{
var stubControllerContext = new Mock<ControllerContext>();
stubControllerContext.Setup(x => x.HttpContext).Returns(new Mock<HttpContextBase>().Object);
stubControllerContext.Setup(x => x.RouteData).Returns(new RouteData());
stubControllerContext.Setup(x => x.Controller).Returns(new Mock<ControllerBase>().Object); ;
var stubViewContext = new Mock<ViewContext>();
stubViewContext.Setup(x => x.View).Returns(new Mock<IView>().Object);
stubViewContext.Setup(x => x.ViewData).Returns(viewData);
stubViewContext.Setup(x => x.TempData).Returns(new TempDataDictionary());
var mockViewDataContainer = new Mock<IViewDataContainer>();
mockViewDataContainer.Setup(v => v.ViewData).Returns(viewData);
return new HtmlHelper<FakeViewModel>(stubViewContext.Object, mockViewDataContainer.Object);
}
}
class FakeHttpContext : HttpContextBase
{
private Dictionary<object, object> _items = new Dictionary<object, object>();
public override IDictionary Items { get { return _items; } }
}
class FakeViewDataContainer : IViewDataContainer
{
private ViewDataDictionary _viewData = new ViewDataDictionary();
public ViewDataDictionary ViewData { get { return _viewData; } set { _viewData = value; } }
}
class FakeController : Controller { }
class FakeView : IView
{
public void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
throw new NotImplementedException();
}
}
public class FakeViewModel {
public SomeEnum Foo { get; set; }
}

How do I use Autofixture (v3) with ICustomization, ISpecimenBuilder to deal with constructor parameter?

I'm trying to overcome a scenario in which a class has a string constructor parameter which cannot be satisfied by any old string generated by Autofixture (the Guid-y looking value).
Before you're tempted to answer simply with a link to Mark Seemann's Ploeh blog entry on Convention-based Customizations, let me say that I've been referencing it and other blog entries of his for this test, which I can't get to pass.
When I step through in debug, I can see that at some point the constructor parameter is passed in with the valid value, but the test still fails with the Guid-y Color value. I think this has something to do with the fact that there is both a 'color' parameter value, and a 'Color' property to be populated by Autofixture. Is it that I've written an ISpecimenBuilder that addresses the constructor parameter, but I'm testing the public property value (two different things)?
I know that all this is overkill for the example, but I envision a more complicated scenario in which using the Build<T>().With() method would not be DRY.
The Failing Test
[Fact]
public void Leaf_Color_Is_Brown()
{
// arrange
var fixture = new Fixture().Customize(new LeafColorCustomization());
// act
var leaf = fixture.Create<Leaf>();
// using .Build<>.With(), test passes
//var leaf = fixture.Build<Leaf>().With(l => l.Color, "brown").CreateAnonymous();
// assert
Assert.True(leaf.Color == "brown");
}
The SUT
public class Leaf
{
public Leaf(string color)
{
if (color != "brown")
throw new ArgumentException(#"NO LEAF FOR YOU!");
this.Color = color;
}
public string Color { get; set; }
}
The CompositeCustomization implementation (I know the AutoMoqCustomization() isn't needed in this example)
public class LeafCustomization : CompositeCustomization
{
public LeafCustomization()
: base(
new LeafColorCustomization(),
new AutoMoqCustomization()) { }
}
The Leaf-specific ICustomization
public class LeafColorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
if (fixture == null)
throw new ArgumentNullException("fixture");
fixture.Customizations.Add(new LeafBuilder());
}
}
The String-constructor-with-name-of-Color-specific ISpecimenBuilder
public class LeafBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen(request);
if (pi.ParameterType != typeof(string) || pi.Name != "color")
return new NoSpecimen(request);
return "brown";
}
}
Solution 1:
Register that the Color writable property should not be assigned any automatic value as part of the post-processing:
internal class LeafColorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Leaf>(c => c
.Without(x => x.Color));
fixture.Customizations.Add(new LeafBuilder());
}
}
Solution 2:
Make the Color property read-only:
public class Leaf
{
private readonly string color;
public Leaf(string color)
{
if (color != "brown")
throw new ArgumentException(#"NO LEAF FOR YOU!");
this.color = color;
}
public string Color
{
get { return this.color; }
}
}
Since the Color property is read-only AutoFixture is not going to assign a value for it.
The above solutions apply also to AutoFixture 2.
Assuming you deal with the property setting stuff separately, here's a constructor argument restriction Customization which does the trick:
class BrownLeavesCustomization : ICustomization
{
void ICustomization.Customize( IFixture fixture )
{
Func<string> notBrownGenerator = fixture.Create<Generator<string>>()
.SkipWhile( x => x == "Brown" )
.First;
fixture.Customizations.Add(
ArgumentGeneratorCustomization<Leaf>.ForConstructorArgument(
"color",
notBrownGenerator ) );
}
static class ArgumentGeneratorCustomization<T>
{
public static ISpecimenBuilder ForConstructorArgument<TArg>( string argumentName, Func<TArg> generator )
{
return new ConstructorArgumentGenerator<TArg>( argumentName, generator );
}
class ConstructorArgumentGenerator<TArg> : ISpecimenBuilder
{
readonly string _argumentName;
readonly Func<TArg> _generator;
public ConstructorArgumentGenerator( string argumentName, Func<TArg> generator )
{
Assert.Contains( argumentName, from ctor in typeof( T ).GetConstructors() from param in ctor.GetParameters() select param.Name );
_argumentName = argumentName;
_generator = generator;
}
object ISpecimenBuilder.Create( object request, ISpecimenContext context )
{
var pi = request as ParameterInfo;
if ( pi == null )
return new NoSpecimen( request );
if ( pi.Member.DeclaringType != typeof( T ) )
return new NoSpecimen( request );
if ( pi.Member.MemberType != MemberTypes.Constructor )
return new NoSpecimen( request );
if ( pi.ParameterType != typeof( TArg ) )
return new NoSpecimen( request );
if ( pi.Name != _argumentName )
return new NoSpecimen( request );
return _generator();
}
}
}
}
Solution: (based on Mark Seemann's comment on this answer)
Accommodate both the constructor parameter and the writeable property in the ISpecimenBuilder implementation, and do nothing other than add the LeafBuilder instance in LeafColorCustomization:
public class LeafBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var paramInfo = request as ParameterInfo;
if (paramInfo != null
&& paramInfo.ParameterType == typeof(string)
&& paramInfo.Name == "color")
{ return "brown"; }
var propInfo = request as PropertyInfo;
if (propInfo != null
&& propInfo.PropertyType == typeof(string)
&& propInfo.Name == "Color")
{ return "brown"; }
return new NoSpecimen(request);
}
}
internal class LeafColorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new LeafBuilder());
}
}

How to find out with which Sitecore site an item is associated?

We have a multi-site solution (Site 1 and Site 2), and I need to be able to determine if an item for which we're getting the URL (in the LinkProvider, which is custom) belongs to the current context site (Sitecore.Context.Site), or is part of a different site. Is there a good way to do this?
Basically, we just need to be able to find out to which site the item is associated. We can do the comparison between that value and the current context site.
I suggest you make an extension method for the Item class that returns a SiteInfo object containing the definition of the site it belongs to.
Unfortunately I don't have my laptop here with all my code, so I just typed it in Visual Studio and made sure it build, but I'm pretty sure it works:
public static class Extensions
{
public static Sitecore.Web.SiteInfo GetSite(this Sitecore.Data.Items.Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
foreach (Sitecore.Web.SiteInfo siteInfo in siteInfoList)
{
if (item.Paths.FullPath.StartsWith(siteInfo.RootPath))
{
return siteInfo;
}
}
return null;
}
}
So now you can call the GetSite() method on all Item objects and retrieve the SiteInfo for that item.
You can use that to check if it matches your Sitecore.Context.Site, for example by doing:
SiteInfo siteInfo = itemYouNeedToCheck.GetSite();
bool isContextSiteItem = Sitecore.Context.Site.SiteInfo.Equals(siteInfo);
EDIT: I just thought that you could also do it shorter, like this:
public static Sitecore.Web.SiteInfo GetSite(this Sitecore.Data.Items.Item itemYouNeedToCheck)
{
return Sitecore.Configuration.Factory.GetSiteInfoList()
.FirstOrDefault(x => itemYouNeedToCheck.Paths.FullPath.StartsWith(x.RootPath));
}
So pick whatever you like best :)
/// <summary>
/// Get the site info from the <see cref="SiteContextFactory"/> based on the item's path.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The <see cref="SiteInfo"/>.</returns>
public static SiteInfo GetSiteInfo(this Item item)
{
return SiteContextFactory.Sites
.Where(s => !string.IsNullOrWhiteSpace(s.RootPath) && item.Paths.Path.StartsWith(s.RootPath, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(s => s.RootPath.Length)
.FirstOrDefault();
}
I upvoted Ruud van Falier's answer then after a few round of testing I realized that it only works in certain scenarios. Couldn't cancel the vote, so I made some modification to the code here:
public static SiteInfo GetSite(this Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
SiteInfo currentSiteinfo = null;
var matchLength = 0;
foreach (var siteInfo in siteInfoList)
{
if (item.Paths.FullPath.StartsWith(siteInfo.RootPath, StringComparison.OrdinalIgnoreCase) && siteInfo.RootPath.Length > matchLength)
{
matchLength = siteInfo.RootPath.Length;
currentSiteinfo = siteInfo;
}
}
return currentSiteinfo;
}
So the issue was that other built-in sites normally have shorter paths like "/sitecore/content" which will match with your content path before it reaches the actual site configuration. So this code is trying to return the best match.
If you are using Sitecore 9.3+ then you will want to use IItemSiteResolver through dependency injection instead.
IItemSiteResolver _siteResolver;
public MyClass(Sitecore.Sites.IItemSiteResolver siteResolver) {
_siteResolver = siteResolver;
}
public void DoWork(Item item) {
Sitecore.Web.SiteInfo site = _siteResolver.ResolveSite(item);
...
}
public static SiteInfo GetSiteInfo(this Item item)
{
return Sitecore.Links.LinkManager.ResolveTargetSite(item);
}
This is what I use for our multisite solution.
The FormatWith is just a helper for string.Format.
public static SiteInfo GetSite(this Item item)
{
List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
SiteInfo site = null;
foreach (SiteInfo siteInfo in siteInfoList)
{
var siteFullPath = "{0}{1}".FormatWith(siteInfo.RootPath, siteInfo.StartItem);
if (string.IsNullOrWhiteSpace(siteFullPath))
{
continue;
}
if (item.Paths.FullPath.StartsWith(siteFullPath, StringComparison.InvariantCultureIgnoreCase))
{
site = siteInfo;
break;
}
}
return site;
}
And to avoid dependencies, for unit test purposes, I've created a method to extract this information from web.config directly:
public static SiteInfoVM GetSiteInfoForPath(string itemPath)
{
var siteInfos = GetSiteInfoFromXml();
return siteInfos
.Where(i => i.RootPath != "/sitecore/content" && itemPath.StartsWith(i.RootPath))
//.Dump("All Matches")
.OrderByDescending(i => i.RootPath.Length).FirstOrDefault();
}
static List<SiteInfoVM> GetSiteInfoFromXml()
{
XmlNode sitesNode = Sitecore.Configuration.ConfigReader.GetConfigNode("sites");//.Dump();
var result = sitesNode.Cast<XmlNode>()
.Where(xn => xn.Attributes != null && xn.Attributes["rootPath"] != null
//&& (xn.Attributes["targetHostName"]!=null || xn.Attributes["name"].Value)
)
.Select(xn => new {
Name = xn.Attributes["name"].Value,
RootPath = xn.Attributes["rootPath"].Value,
StartItem = xn.Attributes["startItem"].Value,
Language = xn.Attributes["language"] != null ? xn.Attributes["language"].Value : null,
TargetHostName = (xn.Attributes["targetHostName"] != null) ? xn.Attributes["targetHostName"].Value : null,
SiteXml = xn.OuterXml
})
.Select(x => new SiteInfoVM(x.Name, x.RootPath, x.StartItem, x.Language, x.TargetHostName, x.SiteXml))
.ToList();
return result;
}
public class SiteInfoVM
{
public SiteInfoVM(string name, string rootPath, string startItem, string lang, string tgtHostName, string siteXml)
{
Name = name;
TargetHostName = tgtHostName;
RootPath = rootPath;
StartItem = startItem;
Language = lang;
SiteXml = siteXml;
}
public string Name { get; set; }
public string RootPath { get; set; }
public string StartItem { get; set; }
public string Language { get; set; }
public string TargetHostName { get;set; }
public string SiteXml { get; set; }
}
public static class SiteResolver
{
public static SiteContext ResolveSitebyItem(Item contentItem)
{
var site = Factory.GetSiteInfoList().FirstOrDefault(
x => contentItem.Paths.Path.Contains(x.RootPath) &&
x.RootPath != "/sitecore/content" &&
x.Domain == "extranet"
);
if (site is SiteInfo siteInfo)
{
return new SiteContext(siteInfo);
}
return Sitecore.Context.Site;
}
}
I believe this is better solution http://firebreaksice.com/sitecore-context-site-resolution/
public static Sitecore.Web.SiteInfo GetSite(Sitecore.Data.Items.Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
foreach (Sitecore.Web.SiteInfo siteInfo in siteInfoList)
{
var homePage = Sitecore.Context.Database.GetItem(siteInfo.RootPath + siteInfo.StartItem);
if (homePage != null && homePage.Axes.IsAncestorOf(item))
{
return siteInfo;
}
}
return null;
}

How can I upload image and post some datas to MVC4 wep api method?

I have tried for days but I couldn't reach any successful result. I need to post images with their information (s.t. created user name).
This is my method;
[HttpPost]
public Task<HttpResponseMessage> PostFile(string createdByName)
{
HttpRequestMessage request = this.Request;
if (!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = System.Configuration.ConfigurationSettings.AppSettings["TempUploadDir"];
var provider = new MultipartFormDataStreamProvider(root);
var task = request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(o =>
{
AddImages(provider.BodyPartFileNames);
string file1 = provider.BodyPartFileNames.First().Value;
// this is the file name on the server where the file was saved
return new HttpResponseMessage()
{
Content = new StringContent("File uploaded.")
};
}
);
return task;
}
And this my TypeFormatterClass which is added global.asax
public class MultiFormDataMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
{
public MultiFormDataMediaTypeFormatter()
: base()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
}
protected override bool CanReadType(Type type)
{
return true;
}
protected override bool CanWriteType(Type type)
{
return false;
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
{
var contents = formatterContext.Request.Content.ReadAsMultipartAsync().Result;
return Task.Factory.StartNew<object>(() =>
{
return new MultiFormKeyValueModel(contents);
});
}
class MultiFormKeyValueModel : IKeyValueModel
{
IEnumerable<HttpContent> _contents;
public MultiFormKeyValueModel(IEnumerable<HttpContent> contents)
{
_contents = contents;
}
public IEnumerable<string> Keys
{
get
{
return _contents.Cast<string>();
}
}
public bool TryGetValue(string key, out object value)
{
value = _contents.FirstDispositionNameOrDefault(key).ReadAsStringAsync().Result;
return true;
}
}
}
When I post images and "createdByName" I can reach images but I couldn't parameters. How can I do this?
Thank you.
To get your createdByName field, inside your ContinueWith :
var parts = o.Result;
HttpContent namePart = parts.FirstDispositionNameOrDefault("createdByName");
if (namePart == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
string name = namePart.ReadAsStringAsync().Result;
For a more detailed example, see :
http://www.asp.net/web-api/overview/working-with-http/html-forms-and-multipart-mime#multipartmime