diff --git a/frontend/src/Components/Form/DeviceInputConnector.js b/frontend/src/Components/Form/DeviceInputConnector.js
index 43e313826..0b0a87959 100644
--- a/frontend/src/Components/Form/DeviceInputConnector.js
+++ b/frontend/src/Components/Form/DeviceInputConnector.js
@@ -2,13 +2,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
-import { fetchOptions, clearOptions } from 'Store/Actions/providerOptionActions';
+import { fetchOptions, clearOptions, defaultState } from 'Store/Actions/providerOptionActions';
import DeviceInput from './DeviceInput';
function createMapStateToProps() {
return createSelector(
(state, { value }) => value,
- (state) => state.providerOptions,
+ (state) => state.providerOptions.devices || defaultState,
(value, devices) => {
return {
@@ -51,7 +51,7 @@ class DeviceInputConnector extends Component {
}
componentWillUnmount = () => {
- this.props.dispatchClearOptions();
+ this.props.dispatchClearOptions({ section: 'devices' });
}
//
@@ -65,6 +65,7 @@ class DeviceInputConnector extends Component {
} = this.props;
dispatchFetchOptions({
+ section: 'devices',
action: 'getDevices',
provider,
providerData
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.css b/frontend/src/Components/Form/EnhancedSelectInput.css
index c3623c199..3d2df1ad3 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.css
+++ b/frontend/src/Components/Form/EnhancedSelectInput.css
@@ -66,3 +66,8 @@
border-radius: 4px;
background-color: $white;
}
+
+.loading {
+ display: inline-block;
+ margin: 5px -5px 5px 0;
+}
diff --git a/frontend/src/Components/Form/EnhancedSelectInput.js b/frontend/src/Components/Form/EnhancedSelectInput.js
index 1e6fb867d..b63fd011b 100644
--- a/frontend/src/Components/Form/EnhancedSelectInput.js
+++ b/frontend/src/Components/Form/EnhancedSelectInput.js
@@ -10,6 +10,7 @@ import { icons, sizes, scrollDirections } from 'Helpers/Props';
import Icon from 'Components/Icon';
import Portal from 'Components/Portal';
import Link from 'Components/Link/Link';
+import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Measure from 'Components/Measure';
import Modal from 'Components/Modal/Modal';
import ModalBody from 'Components/Modal/ModalBody';
@@ -250,6 +251,10 @@ class EnhancedSelectInput extends Component {
this._addListener();
}
+ if (!this.state.isOpen) {
+ this.props.onOpen();
+ }
+
this.setState({ isOpen: !this.state.isOpen });
}
@@ -295,6 +300,7 @@ class EnhancedSelectInput extends Component {
value,
values,
isDisabled,
+ isFetching,
hasError,
hasWarning,
valueOptions,
@@ -355,9 +361,21 @@ class EnhancedSelectInput extends Component {
styles.dropdownArrowContainer
}
>
-
+
+ {
+ isFetching &&
+
+ }
+
+ {
+ !isFetching &&
+
+ }
@@ -483,12 +501,14 @@ EnhancedSelectInput.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
isDisabled: PropTypes.bool.isRequired,
+ isFetching: PropTypes.bool.isRequired,
hasError: PropTypes.bool,
hasWarning: PropTypes.bool,
valueOptions: PropTypes.object.isRequired,
selectedValueOptions: PropTypes.object.isRequired,
selectedValueComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
optionComponent: PropTypes.elementType,
+ onOpen: PropTypes.func,
onChange: PropTypes.func.isRequired
};
@@ -496,6 +516,7 @@ EnhancedSelectInput.defaultProps = {
className: styles.enhancedSelect,
disabledClassName: styles.isDisabled,
isDisabled: false,
+ isFetching: false,
valueOptions: {},
selectedValueOptions: {},
selectedValueComponent: HintedSelectInputSelectedValue,
diff --git a/frontend/src/Components/Form/EnhancedSelectInputConnector.js b/frontend/src/Components/Form/EnhancedSelectInputConnector.js
new file mode 100644
index 000000000..01a7038b9
--- /dev/null
+++ b/frontend/src/Components/Form/EnhancedSelectInputConnector.js
@@ -0,0 +1,159 @@
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { fetchOptions, clearOptions, defaultState } from 'Store/Actions/providerOptionActions';
+import EnhancedSelectInput from './EnhancedSelectInput';
+
+const importantFieldNames = [
+ 'baseUrl',
+ 'apiPath',
+ 'apiKey'
+];
+
+function getProviderDataKey(providerData) {
+ if (!providerData || !providerData.fields) {
+ return null;
+ }
+
+ const fields = providerData.fields
+ .filter((f) => importantFieldNames.includes(f.name))
+ .map((f) => f.value);
+
+ return fields;
+}
+
+function getSelectOptions(items) {
+ if (!items) {
+ return [];
+ }
+
+ return items.map((option) => {
+ return {
+ key: option.value,
+ value: option.name,
+ hint: option.hint,
+ parentKey: option.parentValue
+ };
+ });
+}
+
+function createMapStateToProps() {
+ return createSelector(
+ (state, { selectOptionsProviderAction }) => state.providerOptions[selectOptionsProviderAction] || defaultState,
+ (options) => {
+ if (options) {
+ return {
+ isFetching: options.isFetching,
+ values: getSelectOptions(options.items)
+ };
+ }
+ }
+ );
+}
+
+const mapDispatchToProps = {
+ dispatchFetchOptions: fetchOptions,
+ dispatchClearOptions: clearOptions
+};
+
+class EnhancedSelectInputConnector extends Component {
+
+ //
+ // Lifecycle
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {
+ refetchRequired: false
+ };
+ }
+
+ componentDidMount = () => {
+ this._populate();
+ }
+
+ componentDidUpdate = (prevProps) => {
+ const prevKey = getProviderDataKey(prevProps.providerData);
+ const nextKey = getProviderDataKey(this.props.providerData);
+
+ if (!_.isEqual(prevKey, nextKey)) {
+ this.setState({ refetchRequired: true });
+ }
+ }
+
+ componentWillUnmount = () => {
+ this._cleanup();
+ }
+
+ //
+ // Listeners
+
+ onOpen = () => {
+ if (this.state.refetchRequired) {
+ this._populate();
+ }
+ }
+
+ //
+ // Control
+
+ _populate() {
+ const {
+ provider,
+ providerData,
+ selectOptionsProviderAction,
+ dispatchFetchOptions
+ } = this.props;
+
+ if (selectOptionsProviderAction) {
+ this.setState({ refetchRequired: false });
+ dispatchFetchOptions({
+ section: selectOptionsProviderAction,
+ action: selectOptionsProviderAction,
+ provider,
+ providerData
+ });
+ }
+ }
+
+ _cleanup() {
+ const {
+ selectOptionsProviderAction,
+ dispatchClearOptions
+ } = this.props;
+
+ if (selectOptionsProviderAction) {
+ dispatchClearOptions({ section: selectOptionsProviderAction });
+ }
+ }
+
+ //
+ // Render
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+EnhancedSelectInputConnector.propTypes = {
+ provider: PropTypes.string.isRequired,
+ providerData: PropTypes.object.isRequired,
+ name: PropTypes.string.isRequired,
+ value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
+ values: PropTypes.arrayOf(PropTypes.object).isRequired,
+ selectOptionsProviderAction: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
+ isFetching: PropTypes.bool.isRequired,
+ dispatchFetchOptions: PropTypes.func.isRequired,
+ dispatchClearOptions: PropTypes.func.isRequired
+};
+
+export default connect(createMapStateToProps, mapDispatchToProps)(EnhancedSelectInputConnector);
diff --git a/frontend/src/Components/Form/EnhancedSelectInputOption.js b/frontend/src/Components/Form/EnhancedSelectInputOption.js
index fa12e5b78..84bd62f7a 100644
--- a/frontend/src/Components/Form/EnhancedSelectInputOption.js
+++ b/frontend/src/Components/Form/EnhancedSelectInputOption.js
@@ -32,6 +32,7 @@ class EnhancedSelectInputOption extends Component {
const {
className,
id,
+ depth,
isSelected,
isDisabled,
isHidden,
@@ -54,6 +55,11 @@ class EnhancedSelectInputOption extends Component {
onPress={this.onPress}
>
+ {
+ depth !== 0 &&
+
+ }
+
{
isMultiSelect &&
{label}
{
- dispatch(set({
- section,
- isFetching: false,
- isPopulated: true,
- error: null,
- items: data.options || []
- }));
+ if (lastActions[payload.section]) {
+ if (lastActions[payload.section].actionId === actionId) {
+ lastActions[payload.section] = null;
+ }
+
+ dispatch(set({
+ section: subsection,
+ isFetching: false,
+ isPopulated: true,
+ error: null,
+ items: data.options || []
+ }));
+ }
});
promise.fail((xhr) => {
- dispatch(set({
- section,
- isFetching: false,
- isPopulated: false,
- error: xhr
- }));
+ if (lastActions[payload.section]) {
+ if (lastActions[payload.section].actionId === actionId) {
+ lastActions[payload.section] = null;
+ }
+
+ dispatch(set({
+ section: subsection,
+ isFetching: false,
+ isPopulated: false,
+ error: xhr
+ }));
+ }
});
}
});
@@ -71,8 +100,12 @@ export const actionHandlers = handleThunks({
export const reducers = createHandleActions({
- [CLEAR_OPTIONS]: function(state) {
- return updateSectionState(state, section, defaultState);
+ [CLEAR_OPTIONS]: function(state, { payload }) {
+ const subsection = `${section}.${payload.section}`;
+
+ lastActions[payload.section] = null;
+
+ return updateSectionState(state, subsection, defaultState);
}
-}, defaultState, section);
+}, {}, section);
diff --git a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs
index ba70e9960..a7a9f828b 100644
--- a/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs
+++ b/src/NzbDrone.Core/Annotations/FieldDefinitionAttribute.cs
@@ -19,6 +19,7 @@ namespace NzbDrone.Core.Annotations
public FieldType Type { get; set; }
public bool Advanced { get; set; }
public Type SelectOptions { get; set; }
+ public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public HiddenType Hidden { get; set; }
public PrivacyLevel Privacy { get; set; }
@@ -38,6 +39,15 @@ namespace NzbDrone.Core.Annotations
public string Hint { get; set; }
}
+ public class FieldSelectOption
+ {
+ public int Value { get; set; }
+ public string Name { get; set; }
+ public int Order { get; set; }
+ public string Hint { get; set; }
+ public int? ParentValue { get; set; }
+ }
+
public enum FieldType
{
Textbox,
diff --git a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
index 60761fc2e..75310a5e4 100644
--- a/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
+++ b/src/NzbDrone.Core/Indexers/HttpIndexerBase.cs
@@ -332,7 +332,14 @@ namespace NzbDrone.Core.Indexers
{
var parser = GetParser();
var generator = GetRequestGenerator();
- var releases = FetchPage(generator.GetRecentRequests().GetAllTiers().First().First(), parser);
+ var firstRequest = generator.GetRecentRequests().GetAllTiers().FirstOrDefault()?.FirstOrDefault();
+
+ if (firstRequest == null)
+ {
+ return new ValidationFailure(string.Empty, "No rss feed query available. This may be an issue with the indexer or your indexer category settings.");
+ }
+
+ var releases = FetchPage(firstRequest, parser);
if (releases.Empty())
{
diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs
index b8d51df32..d691e38ee 100644
--- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs
+++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using FluentValidation;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
@@ -133,5 +134,31 @@ namespace NzbDrone.Core.Indexers.Newznab
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}
}
+
+ public override object RequestAction(string action, IDictionary query)
+ {
+ if (action == "newznabCategories")
+ {
+ List categories = null;
+ try
+ {
+ if (Settings.BaseUrl.IsNotNullOrWhiteSpace() && Settings.ApiPath.IsNotNullOrWhiteSpace())
+ {
+ categories = _capabilitiesProvider.GetCapabilities(Settings).Categories;
+ }
+ }
+ catch
+ {
+ // Use default categories
+ }
+
+ return new
+ {
+ options = NewznabCategoryFieldOptionsConverter.GetFieldSelectOptions(categories)
+ };
+ }
+
+ return base.RequestAction(action, query);
+ }
}
}
diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs
index ef6f68ce8..457f3ef29 100644
--- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs
+++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs
@@ -8,6 +8,7 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
+using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Indexers.Newznab
{
@@ -49,6 +50,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
var request = new HttpRequest(url, HttpAccept.Rss);
+ request.AllowAutoRedirect = true;
HttpResponse response;
diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs
new file mode 100644
index 000000000..5b5aee186
--- /dev/null
+++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.UI.WebControls;
+using NzbDrone.Core.Annotations;
+
+namespace NzbDrone.Core.Indexers.Newznab
+{
+ public static class NewznabCategoryFieldOptionsConverter
+ {
+ public static List GetFieldSelectOptions(List categories)
+ {
+ // Ignore categories not relevant for Sonarr
+ var ignoreCategories = new[] { 0, 1000, 2000, 3000, 4000, 6000, 7000 };
+
+ var result = new List();
+
+ if (categories == null)
+ {
+ // Fetching categories failed, use default Newznab categories
+ categories = new List();
+ categories.Add(new NewznabCategory
+ {
+ Id = 5000,
+ Name = "TV",
+ Subcategories = new List
+ {
+ new NewznabCategory { Id = 5070, Name = "Anime" },
+ new NewznabCategory { Id = 5080, Name = "Documentary" },
+ new NewznabCategory { Id = 5020, Name = "Foreign" },
+ new NewznabCategory { Id = 5040, Name = "HD" },
+ new NewznabCategory { Id = 5045, Name = "UHD" },
+ new NewznabCategory { Id = 5050, Name = "Other" },
+ new NewznabCategory { Id = 5030, Name = "SD" },
+ new NewznabCategory { Id = 5060, Name = "Sport" },
+ new NewznabCategory { Id = 5010, Name = "WEB-DL" }
+ }
+ });
+ }
+
+ foreach (var category in categories)
+ {
+ if (ignoreCategories.Contains(category.Id))
+ {
+ continue;
+ }
+
+ result.Add(new FieldSelectOption
+ {
+ Value = category.Id,
+ Name = category.Name,
+ Hint = $"({category.Id})"
+ });
+
+ if (category.Subcategories != null)
+ {
+ foreach (var subcat in category.Subcategories)
+ {
+ result.Add(new FieldSelectOption
+ {
+ Value = subcat.Id,
+ Name = subcat.Name,
+ Hint = $"({subcat.Id})",
+ ParentValue = category.Id
+ });
+ }
+ }
+ }
+
+ result.Sort((l, r) => l.Value.CompareTo(r.Value));
+
+ return result;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
index eddabcccc..88f895f3e 100644
--- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
+++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
@@ -73,10 +73,10 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(2, Label = "API Key", Privacy = PrivacyLevel.ApiKey)]
public string ApiKey { get; set; }
- [FieldDefinition(3, Label = "Categories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows")]
+ [FieldDefinition(3, Label = "Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Comma Separated list, leave blank to disable standard/daily shows")]
public IEnumerable Categories { get; set; }
- [FieldDefinition(4, Label = "Anime Categories", HelpText = "Comma Separated list, leave blank to disable anime")]
+ [FieldDefinition(4, Label = "Anime Categories", Type = FieldType.Select, SelectOptionsProviderAction = "newznabCategories", HelpText = "Comma Separated list, leave blank to disable anime")]
public IEnumerable AnimeCategories { get; set; }
[FieldDefinition(5, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs
index f65d2966a..08f28314f 100644
--- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs
+++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs
@@ -55,17 +55,17 @@ namespace NzbDrone.Core.Indexers.Torznab
private IndexerDefinition GetDefinition(string name, TorznabSettings settings)
{
return new IndexerDefinition
- {
- EnableRss = false,
- EnableAutomaticSearch = false,
- EnableInteractiveSearch = false,
- Name = name,
- Implementation = GetType().Name,
- Settings = settings,
- Protocol = DownloadProtocol.Usenet,
- SupportsRss = SupportsRss,
- SupportsSearch = SupportsSearch
- };
+ {
+ EnableRss = false,
+ EnableAutomaticSearch = false,
+ EnableInteractiveSearch = false,
+ Name = name,
+ Implementation = GetType().Name,
+ Settings = settings,
+ Protocol = DownloadProtocol.Usenet,
+ SupportsRss = SupportsRss,
+ SupportsSearch = SupportsSearch
+ };
}
private TorznabSettings GetSettings(string url, string apiPath = null, int[] categories = null, int[] animeCategories = null)
@@ -124,5 +124,28 @@ namespace NzbDrone.Core.Indexers.Torznab
return new ValidationFailure(string.Empty, "Unable to connect to indexer, check the log for more details");
}
}
+
+ public override object RequestAction(string action, IDictionary query)
+ {
+ if (action == "newznabCategories")
+ {
+ List categories = null;
+ try
+ {
+ categories = _capabilitiesProvider.GetCapabilities(Settings).Categories;
+ }
+ catch
+ {
+ // Use default categories
+ }
+
+ return new
+ {
+ options = NewznabCategoryFieldOptionsConverter.GetFieldSelectOptions(categories)
+ };
+ }
+
+ return base.RequestAction(action, query);
+ }
}
}
diff --git a/src/Sonarr.Api.V3/ProviderModuleBase.cs b/src/Sonarr.Api.V3/ProviderModuleBase.cs
index d15fdf51e..060d024c3 100644
--- a/src/Sonarr.Api.V3/ProviderModuleBase.cs
+++ b/src/Sonarr.Api.V3/ProviderModuleBase.cs
@@ -27,7 +27,7 @@ namespace Sonarr.Api.V3
Get("schema", x => GetTemplates());
Post("test", x => Test(ReadResourceFromRequest(true)));
Post("testall", x => TestAll());
- Post("action/{action}", x => RequestAction(x.action, ReadResourceFromRequest(true)));
+ Post("action/{action}", x => RequestAction(x.action, ReadResourceFromRequest(true, true)));
GetResourceAll = GetAll;
GetResourceById = GetProviderById;
diff --git a/src/Sonarr.Http/ClientSchema/Field.cs b/src/Sonarr.Http/ClientSchema/Field.cs
index b6395a4d6..8fe3336ab 100644
--- a/src/Sonarr.Http/ClientSchema/Field.cs
+++ b/src/Sonarr.Http/ClientSchema/Field.cs
@@ -14,6 +14,7 @@ namespace Sonarr.Http.ClientSchema
public string Type { get; set; }
public bool Advanced { get; set; }
public List SelectOptions { get; set; }
+ public string SelectOptionsProviderAction { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
diff --git a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs
index 213a70905..46f4f5430 100644
--- a/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs
+++ b/src/Sonarr.Http/ClientSchema/SchemaBuilder.cs
@@ -106,7 +106,14 @@ namespace Sonarr.Http.ClientSchema
if (fieldAttribute.Type == FieldType.Select)
{
- field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
+ if (fieldAttribute.SelectOptionsProviderAction.IsNotNullOrWhiteSpace())
+ {
+ field.SelectOptionsProviderAction = fieldAttribute.SelectOptionsProviderAction;
+ }
+ else
+ {
+ field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
+ }
}
if (fieldAttribute.Hidden != HiddenType.Visible)
diff --git a/src/Sonarr.Http/REST/RestModule.cs b/src/Sonarr.Http/REST/RestModule.cs
index e49f850cf..9a6b693f4 100644
--- a/src/Sonarr.Http/REST/RestModule.cs
+++ b/src/Sonarr.Http/REST/RestModule.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
+using FluentValidation.Results;
using Nancy;
using Nancy.Responses.Negotiation;
using Newtonsoft.Json;
@@ -198,7 +199,7 @@ namespace Sonarr.Http.REST
return Negotiate.WithModel(model).WithStatusCode(statusCode);
}
- protected TResource ReadResourceFromRequest(bool skipValidate = false)
+ protected TResource ReadResourceFromRequest(bool skipValidate = false, bool skipSharedValidate = false)
{
TResource resource;
@@ -216,7 +217,12 @@ namespace Sonarr.Http.REST
throw new BadRequestException("Request body can't be empty");
}
- var errors = SharedValidator.Validate(resource).Errors.ToList();
+ var errors = new List();
+
+ if (!skipSharedValidate)
+ {
+ errors.AddRange(SharedValidator.Validate(resource).Errors);
+ }
if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase) && !skipValidate && !Request.Url.Path.EndsWith("/test", StringComparison.InvariantCultureIgnoreCase))
{