Skip to content

Commit 98c14cd

Browse files
committed
LinkBuilder: some fixes and code refactoring
1 parent 88da774 commit 98c14cd

4 files changed

Lines changed: 151 additions & 171 deletions

File tree

src/Libraries/SmartStore.Services/Cms/LinkResolver.cs

Lines changed: 76 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ public partial class LinkResolver : ILinkResolver
2020
{
2121
/// <remarks>
2222
/// {0} : Expression w/o q
23-
/// {1} : q
24-
/// {2} : LanguageId
25-
/// {3} : Store
26-
/// {4} : RolesIdent
23+
/// {1} : LanguageId
24+
/// {2} : Store
25+
/// {3} : RolesIdent
2726
/// </remarks>
2827
internal const string LINKRESOLVER_KEY = "linkresolver:{0}-{1}-{2}-{3}";
2928

@@ -97,18 +96,19 @@ public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable<Cus
9796
else
9897
{
9998
var cacheKey = LINKRESOLVER_KEY.FormatInvariant(
100-
d.Link,
101-
d.QueryString.EmptyNull(),
99+
d.Expression,
102100
languageId,
103101
storeId,
104102
string.Join(",", roles.Where(x => x.Active).Select(x => x.Id)));
105103

106-
_services.Cache.Get(cacheKey, () =>
104+
d = _services.Cache.Get(cacheKey, () =>
107105
{
108-
switch (d.Type)
106+
var d2 = d.Clone();
107+
108+
switch (d2.Type)
109109
{
110110
case LinkType.Product:
111-
GetEntityData<Product>(d, languageId, x => new ResolverEntitySummary
111+
GetEntityData<Product>(d2, languageId, x => new ResolverEntitySummary
112112
{
113113
Name = x.Name,
114114
Published = x.Published,
@@ -118,7 +118,7 @@ public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable<Cus
118118
});
119119
break;
120120
case LinkType.Category:
121-
GetEntityData<Category>(d, languageId, x => new ResolverEntitySummary
121+
GetEntityData<Category>(d2, languageId, x => new ResolverEntitySummary
122122
{
123123
Name = x.Name,
124124
Published = x.Published,
@@ -128,7 +128,7 @@ public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable<Cus
128128
});
129129
break;
130130
case LinkType.Manufacturer:
131-
GetEntityData<Manufacturer>(d, languageId, x => new ResolverEntitySummary
131+
GetEntityData<Manufacturer>(d2, languageId, x => new ResolverEntitySummary
132132
{
133133
Name = x.Name,
134134
Published = x.Published,
@@ -137,13 +137,13 @@ public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable<Cus
137137
});
138138
break;
139139
case LinkType.Topic:
140-
GetEntityData<Topic>(d, languageId, x => null);
140+
GetEntityData<Topic>(d2, languageId, x => null);
141141
break;
142142
default:
143143
throw new SmartException("Unknown link builder type.");
144144
}
145145

146-
return d;
146+
return d2;
147147
});
148148
}
149149

@@ -180,36 +180,74 @@ public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable<Cus
180180
return result;
181181
}
182182

183+
private bool TokenizeExpression(string expression, out string type, out string path, out string query)
184+
{
185+
type = null;
186+
path = null;
187+
query = null;
188+
189+
if (string.IsNullOrWhiteSpace(expression))
190+
{
191+
return false;
192+
}
193+
194+
var colonIndex = expression.IndexOf(':');
195+
if (colonIndex > -1)
196+
{
197+
type = expression.Substring(0, colonIndex);
198+
if (type.StartsWith("http", StringComparison.OrdinalIgnoreCase))
199+
{
200+
type = null;
201+
colonIndex = -1;
202+
}
203+
}
204+
205+
path = expression.Substring(colonIndex + 1);
206+
207+
var qmIndex = path.IndexOf('?');
208+
if (qmIndex > -1)
209+
{
210+
query = path.Substring(qmIndex + 1);
211+
path = path.Substring(0, qmIndex);
212+
}
213+
214+
return true;
215+
}
216+
183217
protected virtual LinkResolverData Parse(string linkExpression)
184218
{
185-
if (!string.IsNullOrWhiteSpace(linkExpression))
186-
{
187-
var index = linkExpression.IndexOf(':');
219+
if (TokenizeExpression(linkExpression, out var type, out var path, out var query))
220+
{
221+
if (!string.IsNullOrWhiteSpace(type) && Enum.TryParse(type, true, out LinkType linkType))
222+
{
223+
var result = new LinkResolverData { Type = linkType, Expression = string.Concat(type, ":", path) };
188224

189-
if (index != -1 && Enum.TryParse(linkExpression.Substring(0, index), true, out LinkType type))
190-
{
191-
var rawValue = linkExpression.Substring(index + 1);
192-
rawValue.SplitToPair(out var value, out var queryString, "?", true);
225+
switch (linkType)
226+
{
227+
case LinkType.Product:
228+
case LinkType.Category:
229+
case LinkType.Manufacturer:
230+
case LinkType.Topic:
231+
var id = path.ToInt();
232+
result.Value = id != 0 ? (object)id : path;
233+
result.QueryString = query;
234+
break;
235+
case LinkType.Url:
236+
result.Value = path + (query.HasValue() ? "?" + query : "");
237+
break;
238+
case LinkType.File:
239+
result.Value = path;
240+
result.QueryString = query;
241+
break;
242+
default:
243+
throw new SmartException("Unknown link builder type.");
244+
}
193245

194-
switch (type)
195-
{
196-
case LinkType.Product:
197-
case LinkType.Category:
198-
case LinkType.Manufacturer:
199-
case LinkType.Topic:
200-
var id = value.ToInt();
201-
return new LinkResolverData { Type = type, Value = id != 0 ? (object)id : value, QueryString = queryString };
202-
case LinkType.Url:
203-
return new LinkResolverData { Type = type, Value = rawValue };
204-
case LinkType.File:
205-
return new LinkResolverData { Type = type, Value = value, QueryString = queryString };
206-
default:
207-
throw new SmartException("Unknown link builder type.");
208-
}
209-
}
210-
}
246+
return result;
247+
}
248+
}
211249

212-
return new LinkResolverData { Type = LinkType.Url, Value = linkExpression.EmptyNull() };
250+
return new LinkResolverData { Type = LinkType.Url, Value = linkExpression.EmptyNull() };
213251
}
214252

215253
internal void GetEntityData<T>(LinkResolverData data, int languageId, Expression<Func<T, ResolverEntitySummary>> selector) where T : BaseEntity

src/Libraries/SmartStore.Services/Cms/LinkResolverResult.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ public class LinkResolverResult : IHtmlString
2525
{
2626
private string _link;
2727

28-
public LinkType Type { get; set; }
28+
/// <summary>
29+
/// The raw expression without query string
30+
/// </summary>
31+
public string Expression { get; set; }
32+
33+
/// <summary>
34+
/// The query string part
35+
/// </summary>
36+
public string QueryString { get; set; }
37+
38+
public LinkType Type { get; set; }
2939
public object Value { get; set; }
3040
public string Slug { get; set; }
31-
public string QueryString { get; set; }
3241

3342
public LinkStatus Status { get; set; }
3443
public string Label { get; set; }
@@ -97,9 +106,19 @@ public override string ToString()
97106
}
98107

99108
[Serializable]
100-
public partial class LinkResolverData : LinkResolverResult
109+
public partial class LinkResolverData : LinkResolverResult, ICloneable<LinkResolverData>
101110
{
102111
public bool SubjectToAcl { get; set; }
103112
public bool LimitedToStores { get; set; }
104-
}
113+
114+
public LinkResolverData Clone()
115+
{
116+
return (LinkResolverData)this.MemberwiseClone();
117+
}
118+
119+
object ICloneable.Clone()
120+
{
121+
return this.MemberwiseClone();
122+
}
123+
}
105124
}

src/Presentation/SmartStore.Web.Framework/Extensions/UrlHelperExtensions.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Web;
23
using System.Web.Mvc;
34
using SmartStore.Core.Domain.Media;
45
using SmartStore.Core.Infrastructure;
@@ -48,6 +49,56 @@ public static string Picture(this UrlHelper urlHelper, Picture picture, int targ
4849
return pictureService.GetUrl(picture, targetSize, fallbackType, host);
4950
}
5051

52+
/// <summary>
53+
/// Resolves a link to a system internal entity like product, topic, category or manufacturer.
54+
/// </summary>
55+
/// <param name="expression">A link expression as supported by the <see cref="ILinkResolver"/></param>
56+
/// <param name="q">Optional query string, which should be appended to the resolved link.</param>
57+
/// <returns>Link</returns>
58+
/// <remarks>
59+
/// This method returns an empty string in following cases:
60+
/// - the requested entity does not exist.
61+
/// - the current user has no permission to acces the entity.
62+
/// </remarks>
63+
public static IHtmlString Entity(this UrlHelper urlHelper, string expression, string q = null)
64+
{
65+
Guard.NotEmpty(expression, nameof(expression));
66+
67+
var linkResolver = EngineContext.Current.Resolve<ILinkResolver>();
68+
var link = linkResolver.Resolve(expression);
69+
70+
if (link.Status == LinkStatus.Ok)
71+
{
72+
return link;
73+
}
74+
75+
return MvcHtmlString.Empty;
76+
}
77+
78+
/// <summary>
79+
/// Resolves a link label for a system internal entity like product, topic, category or manufacturer.
80+
/// The label is either the entity short title, title or name, whichever is applicable.
81+
/// </summary>
82+
/// <param name="expression">A link expression as supported by the <see cref="ILinkResolver"/></param>
83+
/// <returns>Label</returns>
84+
/// <remarks>
85+
/// This method returns an empty string if the requested entity does not exist.
86+
/// </remarks>
87+
public static IHtmlString EntityLabel(this UrlHelper urlHelper, string expression)
88+
{
89+
Guard.NotEmpty(expression, nameof(expression));
90+
91+
var linkResolver = EngineContext.Current.Resolve<ILinkResolver>();
92+
var link = linkResolver.Resolve(expression);
93+
94+
if (link.Status == LinkStatus.Ok || link.Status == LinkStatus.Forbidden)
95+
{
96+
return MvcHtmlString.Create(link.Label);
97+
}
98+
99+
return MvcHtmlString.Empty;
100+
}
101+
51102
public static string TopicUrl(this UrlHelper urlHelper, string systemName, bool popup = false)
52103
{
53104
Guard.NotEmpty(systemName, nameof(systemName));

0 commit comments

Comments
 (0)