Switch between UserControls in View based on property of ViewModel - data-binding

How can I change between UserControls in the View, based on a property of the ViewModel, in a Windows 8 store app?
Say my ViewModel has a property which looks something like this:
class MyViewModel
{
public string CurrentStatus
{
get { return (string)GetValue(CurrentStatusProperty); }
set { SetValue(CurrentStatusProperty, value); }
}
public static readonly DependencyProperty CurrentStatusProperty =
DependencyProperty.Register("CurrentStatus", typeof(string), typeof(MyViewModel), new PropertyMetadata("default", CurrentStatusChanged));
...
}
How do I make my View change an UserControl according to the value of the CurrentStatus from the ViewModel?
The strightforward solution for me would have been to create a binding between the CurrentStatus from the ViewModel and another string from the View, but apparently data binding can only be used for a DependencyObject (which a string isn't).
Edit:
The xaml file contains just a StackPanel in which I want to put a UserControl, based on the CurrentStatus. So, if the CurrentStatus is "one" I want the StackPanel to contain UserControlOne and so on...
Any ideas or nice solutions to this problem?
Thanks a lot!

I usually use a ContentControl and set it's ContentTemplate based on a DataTrigger
I have an example in my blog post Switching between Views/UserControls using MVVM, but here's a sample of what that may look like using your scenario:
<DataTemplate x:Key="DefaultTemplate">
<local:DefaultUserControl />
</DataTemplate>
<DataTemplate x:Key="ClosedTemplate">
<local:ClosedUserControl />
</DataTemplate>
<Style x:Key="MyContentControlStyle" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentStatus}" Value="Closed">
<Setter Property="ContentTemplate" Value="{StaticResource ClosedTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ContentControl Style="{StaticResource MyContentControlStyle}" />
EDIT
Apparently DataTriggers are not supported in WinRT, and have been replaced with the VisualStateManager. I haven't had the chance to use this yet, however from what I'm reading they used the same approach for WinRT as they did Silverlight (which also didn't support DataTriggers until v5), and my solution for Silverlight was to use a DataTemplateSelector
I hope that can point you in the right direction :)

Not sure I fully understand what you are trying to do. Can you post the xaml?
If what you are wanting is to present the controls differently based on the status, then use a converter and you can present a different template based upon the status:
public class StatusToTemplateConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((string) value)
{
case "Status1":
return Application.Current.Resources["Status1Template"];
case "Status2":
return Application.Current.Resources["Status2Template"];
default:
return Application.Current.Resources["Status3Template"];
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
#endregion
}
the above assumes you have your templates defined in a resource file
If all you ae wanting to do is something simpler such as red text for Status1 or Green text for status2 you could have a converter that just converts the status to a colour and bind the FontColor to the status and use the converter.
But like I said, without more code being posted, I'm not 100% clear on what you are trying to achieve

While I've used the DataTemplateSelector and IValueConverter in such cases before - now my favorite approach is using the VisualStateManager. I have created a most basic attached property that implements the attached behavior pattern in WinRT XAML Toolkit here - that looks like this:
/// <summary>
/// Defines an attached property that controls the visual state of the element based on the value.
/// </summary>
public static class VisualStateExtensions
{
#region State
/// <summary>
/// State Attached Dependency Property
/// </summary>
public static readonly DependencyProperty StateProperty =
DependencyProperty.RegisterAttached(
"State",
typeof(string),
typeof(VisualStateExtensions),
new PropertyMetadata(null, OnStateChanged));
/// <summary>
/// Gets the State property. This dependency property
/// indicates the VisualState that the associated control should be set to.
/// </summary>
public static string GetState(DependencyObject d)
{
return (string)d.GetValue(StateProperty);
}
/// <summary>
/// Sets the State property. This dependency property
/// indicates the VisualState that the associated control should be set to.
/// </summary>
public static void SetState(DependencyObject d, string value)
{
d.SetValue(StateProperty, value);
}
/// <summary>
/// Handles changes to the State property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnStateChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var stateName = (string)e.NewValue;
var ctrl = (Control)d;
VisualStateManager.GoToState(ctrl, stateName, true);
}
#endregion
}
You should define an enum-style class like the VisualStates class in Silverlight Toolkit that lists all your visual states (so you don't have duplicates), e.g.
internal static class VisualStates
{
#region GroupCommon
/// <summary>
/// Common state group.
/// </summary>
public const string GroupCommon = "CommonStates";
/// <summary>
/// Normal state of the Common state group.
/// </summary>
public const string StateNormal = "Normal";
/// <summary>
/// Normal state of the Common state group.
/// </summary>
public const string StateReadOnly = "ReadOnly";
/// <summary>
/// MouseOver state of the Common state group.
/// </summary>
public const string StateMouseOver = "MouseOver";
/// <summary>
/// Pressed state of the Common state group.
/// </summary>
public const string StatePressed = "Pressed";
/// <summary>
/// Disabled state of the Common state group.
/// </summary>
public const string StateDisabled = "Disabled";
#endregion GroupCommon
#region GroupFocus
/// <summary>
/// Focus state group.
/// </summary>
public const string GroupFocus = "FocusStates";
/// <summary>
/// Unfocused state of the Focus state group.
/// </summary>
public const string StateUnfocused = "Unfocused";
/// <summary>
/// Focused state of the Focus state group.
/// </summary>
public const string StateFocused = "Focused";
#endregion GroupFocus
}
Once you have these - you can have a property in your view model like
public string VisualState { get; set; /* Raise PropertyChange event your preferred way here */ }
And in your view say
<Page
...
xmlns:extensions="using:WinRTXamlToolkit.Controls.Extensions"
extensions:VisualStateExtensions.State="{Binding VisualState}">...
Then your view model can easily drive the change of the visual state and you can use Blend to define what that state looks like - e.g. change the Content or ContentTemplate or simply visibility of various elements in the layout. I think this method has best support of the designer tools since you can flip between views with a click of a button and make updates to these views in Blend.

Related

I have a very strange question with xamarin.form and libvlcsharp

When I create LibVLC and MediaPlayer objects in the constructor and play the video, there is only sound but no image. When I create LibVLC and MediaPlayer objects in the'Play' function, there are sounds and images. I don't want to create a MediaPlayer object every time the play function is called. what should I do?
using System;
using System.ComponentModel;
using System.Windows.Input;
using LibVLCSharp.Shared;
using static Xamarin.Essentials.Permissions;
namespace MediaElement
{
/// <summary>
/// Represents the main viewmodel.
/// </summary>
public class MainViewModel : INotifyPropertyChanged
{
/// <summary>
/// Property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Initializes a new instance of <see cref="MainViewModel"/> class.
/// </summary>
public MainViewModel()
{
Core.Initialize();
LibVLC = new LibVLC();
MediaPlayer = new MediaPlayer(LibVLC) { EnableHardwareDecoding = true };
}
private LibVLC _libVLC;
/// <summary>
/// Gets the <see cref="LibVLCSharp.Shared.LibVLC"/> instance.
/// </summary>
public LibVLC LibVLC
{
get => _libVLC;
private set => Set(nameof(LibVLC), ref _libVLC, value);
}
private MediaPlayer _mediaPlayer;
/// <summary>
/// Gets the <see cref="LibVLCSharp.Shared.MediaPlayer"/> instance.
/// </summary>
public MediaPlayer MediaPlayer
{
get => _mediaPlayer;
private set => Set(nameof(MediaPlayer), ref _mediaPlayer, value);
}
/// <summary>
/// Initialize LibVLC and playback when page appears
/// </summary>
public void Play(String path)
{
MediaPlayer.Play(new LibVLCSharp.Shared.Media(LibVLC, new Uri(path)));
}
private void Set<T>(string propertyName, ref T field, T value)
{
if (field == null && value != null || field != null && !field.Equals(value))
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
You only sent half of your code, we don't know how you are using your VideoView control.
As a general rule of thumb:
How do you attach the MediaPlayer to your view?
Do you properly wait for the view to be visible before calling Play?
Have a look at the samples here and compare to see what you did wrong.

Xamarin.forms Material entry placeholder size

In my xamarin.forms App I am using material entry provided by xamarin.The control works fine. But how can I change the fontsize of placeholder?In ios the font size of placeholder is perfect, but in android it is much larger. And also the underline of the entry is little more in the left side.How to reduce the gap and align placeholder and underline equally(Refer Image)
What I Have done
What I am trying to achieve
My entry
<Entry x:Name="CustomerEntry" Visual="Material" FontSize="Small" Placeholder="Customer Name" HorizontalOptions="FillAndExpand" BackgroundColor="Transparent" TextColor="White" PlaceholderColor="White" Margin="0,0,0,0" ></Entry>
Any help is appreciated
For the font size, I believe that you'll need to custom render. See this answer for details on how to do that. Make sure to inherit from the Material Render.
To solve the left alignment issue, I couldn't find a good solution that worked for both Android and iOS, so what I did was to suck the left margin of the entry over and then use a box view to cover the underline so everything looks left aligned. I had do this in multiple places, so I created a custom control. The downside to this was passing all the bindings through.
My custom control Xaml:
<?xml version="1.0" encoding="UTF-8" ?>
<Grid x:Class="x.x.x.Controls.MaterialControls.MaterialEntry"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Name="MaterialEntryComponent"
Margin="-16,0,0,0"
Focused="OnComponentFocused">
<Entry x:Name="MaterialEntryEntry"
Grid.Row="0"
BackgroundColor="{Binding EntryBackgroundColor}"
BindingContext="{x:Reference MaterialEntryComponent}"
Completed="OnEntryCompleted"
FontFamily="{Binding FontFamily}"
FontSize="{Binding FontSize}"
IsPassword="{Binding IsPassword}"
IsSpellCheckEnabled="{Binding IsSpellCheckEnabled}"
IsTextPredictionEnabled="{Binding IsTextPredictionEnabled}"
Keyboard="{Binding Keyboard}"
MaxLength="{Binding MaxLength}"
Placeholder="{Binding Placeholder}"
PlaceholderColor="{Binding PlaceholderColor}"
ReturnType="{Binding ReturnType}"
Text="{Binding Text}"
TextChanged="OnEntryTextChanged"
TextColor="{Binding TextColor}"
Visual="Material" />
<BoxView Grid.Row="0"
Margin="{OnPlatform iOS='0,0,0,-1'}"
BackgroundColor="{Binding EntryBackgroundColor}"
BindingContext="{x:Reference MaterialEntryComponent}"
HorizontalOptions="Start"
VerticalOptions="FillAndExpand"
WidthRequest="16" />
</Grid>
The bulk of the work is being done by the negative left margin on the parent grid, and the width of 16 on the BoxView. I did find out I needed to move the margin of the BoxView down by 1 on iOS to cover the underline.
And the code behind:
using System;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace x.x.x.Controls.MaterialControls
{
/// <summary>
/// Code behind for the Material Entry control.
/// </summary>
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MaterialEntry : Grid
{
#region Event Handlers
/// <summary>
/// Completed event. Fires when the return key on the keyboard is pressed.
/// </summary>
public event EventHandler Completed;
/// <summary>
/// Text changed event. Fires when the text on the entry control is changed.
/// </summary>
public event EventHandler<TextChangedEventArgs> TextChanged;
#endregion
#region Bindable Properties
/// <summary>
/// Bindable property for the entry background color on the view.
/// </summary>
public static readonly BindableProperty EntryBackgroundColorProperty =
BindableProperty.Create(nameof(EntryBackgroundColor), typeof(Color), typeof(MaterialEntry), Color.White);
/// <summary>
/// Bindable property for the font family of the entry on the view..
/// </summary>
public static readonly BindableProperty FontFamilyProperty =
BindableProperty.Create(nameof(FontFamily), typeof(string), typeof(MaterialEntry), default(string));
/// <summary>
/// Bindable property for the font size of the entry on the view..
/// </summary>
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(MaterialEntry), 12.0);
/// <summary>
/// Bindable property for the IsPassword of the entry on the view.
/// </summary>
public static readonly BindableProperty IsPasswordProperty =
BindableProperty.Create(nameof(IsPassword), typeof(bool), typeof(MaterialEntry), false);
/// <summary>
/// Bindable property for the IsSpellCheckEnabled of the entry on the view.
/// </summary>
public static readonly BindableProperty IsSpellCheckEnabledProperty =
BindableProperty.Create(nameof(IsSpellCheckEnabled), typeof(bool), typeof(MaterialEntry), true);
/// <summary>
/// Bindable property for the IsTextPredictionEnabled of the entry on the view.
/// </summary>
public static readonly BindableProperty IsTextPredictionEnabledProperty =
BindableProperty.Create(nameof(IsTextPredictionEnabled), typeof(bool), typeof(MaterialEntry), true);
/// <summary>
/// Bindable property for the keyboard type of the entry on the view.
/// </summary>
public static readonly BindableProperty KeyboardProperty =
BindableProperty.Create(nameof(Keyboard), typeof(Keyboard), typeof(MaterialEntry), Keyboard.Default);
/// <summary>
/// Bindable property for the MaxLength of the entry on the view..
/// </summary>
public static readonly BindableProperty MaxLengthProperty =
BindableProperty.Create(nameof(MaxLength), typeof(int), typeof(MaterialEntry), int.MaxValue);
/// <summary>
/// Bindable property for the placeholder text of the entry on the view.
/// </summary>
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(MaterialEntry), default(string));
/// <summary>
/// Bindable property for the placeholder text color of the entry on the view.
/// </summary>
public static readonly BindableProperty PlaceholderColorProperty =
BindableProperty.Create(nameof(PlaceholderColor), typeof(Color), typeof(MaterialEntry), Color.Black);
/// <summary>
/// Bindable property for the return command of the entry on the view.
/// </summary>
public static readonly BindableProperty ReturnCommandProperty =
BindableProperty.Create(nameof(ReturnCommand), typeof(ICommand), typeof(Entry), default(ICommand));
/// <summary>
/// Bindable property for the return command parameter of the entry on the view.
/// </summary>
public static readonly BindableProperty ReturnCommandParameterProperty =
BindableProperty.Create(nameof(ReturnCommandParameter), typeof(object), typeof(Entry), default(object));
/// <summary>
/// Bindable property for the return type of the entry on the view.
/// </summary>
public static readonly BindableProperty ReturnTypeProperty =
BindableProperty.Create(nameof(ReturnType), typeof(ReturnType), typeof(Entry), ReturnType.Default);
/// <summary>
/// Bindable property for the text of the entry on the view.
/// </summary>
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text), typeof(string), typeof(MaterialEntry), default(string), BindingMode.TwoWay);
/// <summary>
/// Bindable property for the text color of the entry on the view.
/// </summary>
public static readonly BindableProperty TextColorProperty =
BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(MaterialEntry), Color.Black);
#endregion
#region Properties
/// <summary>
/// The background color of the entry control. Default is <see cref="Color.White"/>.
/// </summary>
public Color EntryBackgroundColor
{
get => (Color)GetValue(EntryBackgroundColorProperty);
set => SetValue(EntryBackgroundColorProperty, value);
}
/// <summary>
/// The font family for the entry control to use.
/// </summary>
public string FontFamily
{
get => (string)GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
/// <summary>
/// The font size of the entry control. Default is 12.0.
/// </summary>
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get => (double)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
/// <summary>
/// Set if the entry field is a password field. Default is false.
/// </summary>
public bool IsPassword
{
get => (bool)GetValue(IsPasswordProperty);
set => SetValue(IsPasswordProperty, value);
}
/// <summary>
/// Set if spell check is enabled on the entry. Default is True.
/// </summary>
public bool IsSpellCheckEnabled
{
get => (bool)GetValue(IsSpellCheckEnabledProperty);
set => SetValue(IsSpellCheckEnabledProperty, value);
}
/// <summary>
/// Set if text prediction is enabled on the entry. Default is True.
/// </summary>
public bool IsTextPredictionEnabled
{
get => (bool)GetValue(IsTextPredictionEnabledProperty);
set => SetValue(IsTextPredictionEnabledProperty, value);
}
/// <summary>
/// The type of keyboard to use for the entry control. Default is <see cref="Keyboard.Default"/>.
/// </summary>
public Keyboard Keyboard
{
get => (Keyboard)GetValue(KeyboardProperty);
set => SetValue(KeyboardProperty, value);
}
/// <summary>
/// The maximum allowed length of input for the entry. Default is <see cref="int.MaxValue"/>.
/// </summary>
public int MaxLength
{
get => (int)GetValue(MaxLengthProperty);
set => SetValue(MaxLengthProperty, value);
}
/// <summary>
/// The text to use for the placeholder.
/// </summary>
public string Placeholder
{
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
/// <summary>
/// The color of the placeholder text. Default is <see cref="Color.Black"/>.
/// </summary>
public Color PlaceholderColor
{
get => (Color)GetValue(PlaceholderColorProperty);
set => SetValue(PlaceholderColorProperty, value);
}
/// <summary>
/// The command that fires when the return button on the keyboard is tapped.
/// </summary>
public ICommand ReturnCommand
{
get => (ICommand)GetValue(ReturnCommandProperty);
set => SetValue(ReturnCommandProperty, value);
}
/// <summary>
/// The parameter to pass with the return command.
/// </summary>
public object ReturnCommandParameter
{
get => GetValue(ReturnCommandParameterProperty);
set => SetValue(ReturnCommandParameterProperty, value);
}
/// <summary>
/// The type of return button to display on the keyboard. Default is <see cref="ReturnType.Default"/>.
/// </summary>
public ReturnType ReturnType
{
get => (ReturnType)GetValue(ReturnTypeProperty);
set => SetValue(ReturnTypeProperty, value);
}
/// <summary>
/// The text of the entry.
/// </summary>
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// The color of the text. Default is <see cref="Color.Black"/>.
/// </summary>
public Color TextColor
{
get => (Color)GetValue(TextColorProperty);
set => SetValue(TextColorProperty, value);
}
#endregion
/// <summary>
/// Constructor.
/// </summary>
public MaterialEntry()
{
InitializeComponent();
}
#region Methods
/// <summary>
/// Focuses the entry control.
/// </summary>
public void FocusEntry()
{
MaterialEntryEntry.Focus();
}
private void OnEntryCompleted(object sender, EventArgs e)
{
SendCompleted();
}
private void OnEntryTextChanged(object sender, TextChangedEventArgs e)
{
TextChanged?.Invoke(this, e);
}
private void SendCompleted()
{
if (IsEnabled)
{
Completed?.Invoke(this, EventArgs.Empty);
if (ReturnCommand != null && ReturnCommand.CanExecute(ReturnCommandParameter))
{
ReturnCommand.Execute(ReturnCommandParameter);
}
}
}
private void OnComponentFocused(object sender, FocusEventArgs e)
{
FocusEntry();
}
#endregion
}
}
To use the control, remember to import the namespace it into your view with:
xmlns:material="clr-namespace:x.x.x.Controls.MaterialControls".
Then using it would be something like:
<material:MaterialEntry Placeholder="Do something amazing"
Style="{StaticResource EntryStyle}"
Text="{Binding MyEntryText}" />

How do I properly get the entity framework to use my composite primary key?

I have a table defined as such:
CREATE TABLE [dbo].[Sitemap](
[EntityId] [int] NOT NULL,
[PageUrl] [nvarchar](400) NOT NULL,
[Frequency] [nvarchar](400) NOT NULL,
[PageType] [nvarchar](400) NOT NULL,
[UpdatedOn] [datetime] NOT NULL,
CONSTRAINT [PK_Sitemap] PRIMARY KEY CLUSTERED
(
[EntityId] ASC,
[PageType] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
I went to my edmx file and did an update from database. It imported everything but didn't mark both fields as a primary key. I tried added the second field as a primary key, but it still won't treat them as composites.
The issue I am having is when I run an update at the table that has a similar entry such as
EntityId = 1, PageType = 'Product',....
and
EntityId = 1, PageType = 'Artist',...
I am getting an error saying:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
How do I get my Model or code to properly use the composite as the primary key? Or do I have to make some kind of composite field to do this?
UPDATE
My Code is a fork from NopCommerce 1.90
As such I have added an interface and service and registered that with the IoC resolver.
I have the following class that for this table:
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.nopCommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): _______.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using NopSolutions.NopCommerce.BusinessLogic.CustomerManagement;
using NopSolutions.NopCommerce.BusinessLogic.Infrastructure;
using NopSolutions.NopCommerce.BusinessLogic.Payment;
using NopSolutions.NopCommerce.BusinessLogic.Promo.Affiliates;
using NopSolutions.NopCommerce.BusinessLogic.Promo.Discounts;
using NopSolutions.NopCommerce.BusinessLogic.Shipping;
using NopSolutions.NopCommerce.BusinessLogic.Tax;
namespace NopSolutions.NopCommerce.BusinessLogic.SEO.Sitemaps
{
/// <summary>
/// Represents a Sitemap
/// </summary>
[Serializable]
public partial class Sitemap : BaseEntity
{
#region Utilities
#endregion
#region Properties
/// <summary>
/// Gets or Sets EntityId (Product ID, Show ID, etc.)
/// </summary>
public int EntityId { get; set; }
/// <summary>
/// Gets or sets the Page Url
/// </summary>
public string PageUrl { get; set; }
/// <summary>
/// Gets of set the Page Frequency (Should be one of the following - Always, Hourly, Daily, Weekly, Monthly, Yearly, Never)
/// </summary>
public string Frequency { get; set; }
/// <summary>
/// Gets or sets the page type. For example: Product, or Show
/// </summary>
public string PageType { get; set; }
/// <summary>
/// Gets or sets the date and time of sitemap entry update
/// </summary>
public DateTime UpdatedOn { get; set; }
#endregion
}
}
Then I have the interface and service:
Interface:
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.nopCommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): _______.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using NopSolutions.NopCommerce.BusinessLogic.CustomerManagement;
using NopSolutions.NopCommerce.BusinessLogic.Payment;
using NopSolutions.NopCommerce.BusinessLogic.Shipping;
using NopSolutions.NopCommerce.Common;
namespace NopSolutions.NopCommerce.BusinessLogic.SEO.Sitemaps
{
/// <summary>
/// Sitemap service
/// </summary>
public partial interface ISiteMapService
{
/// <summary>
/// Gets a Sitemap entry based on url.
/// </summary>
/// <param name="url">Fully Qualified URL (e.g. http://www.onlinesheetmusic.com/default.aspx)</param>
/// <returns></returns>
Sitemap GetSitemap(string url);
/// <summary>
/// Gets the most recent entry to the sitemap table
/// </summary>
/// <returns></returns>
Sitemap GetLastAddedSitemap(string pageType);
/// <summary>
/// Gets a list of Sitemap entries that are in the init state of the given Page Type
/// </summary>
/// <param name="pageType">Page Type</param>
/// <returns></returns>
List<Sitemap> GetInitEntries(string pageType);
/// <summary>
/// Bool to determine if a given pageType has any entries in the init state.
/// </summary>
/// <param name="pageType"></param>
/// <returns></returns>
bool IsInit(string pageType);
void InitSitemap(string pageType, int startProdId = 0);
/// <summary>
/// Inserts a sitemap entry
/// </summary>
/// <param name="order">Order</param>
void InsertSitemap(Sitemap sm);
/// <summary>
/// Updates the order
/// </summary>
/// <param name="order">The order</param>
void UpdateSitemap(Sitemap sm);
}
}
Service:
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.nopCommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): _______.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using NopSolutions.NopCommerce.BusinessLogic.Audit;
using NopSolutions.NopCommerce.BusinessLogic.Caching;
using NopSolutions.NopCommerce.BusinessLogic.Configuration.Settings;
using NopSolutions.NopCommerce.BusinessLogic.CustomerManagement;
using NopSolutions.NopCommerce.BusinessLogic.Data;
using NopSolutions.NopCommerce.BusinessLogic.Directory;
using NopSolutions.NopCommerce.BusinessLogic.Infrastructure;
using NopSolutions.NopCommerce.BusinessLogic.Localization;
using NopSolutions.NopCommerce.BusinessLogic.Messages;
using NopSolutions.NopCommerce.BusinessLogic.Messages.SMS;
using NopSolutions.NopCommerce.BusinessLogic.Payment;
using NopSolutions.NopCommerce.BusinessLogic.Products;
using NopSolutions.NopCommerce.BusinessLogic.Products.Attributes;
using NopSolutions.NopCommerce.BusinessLogic.Profile;
using NopSolutions.NopCommerce.BusinessLogic.Promo.Discounts;
using NopSolutions.NopCommerce.BusinessLogic.QuickBooks;
using NopSolutions.NopCommerce.BusinessLogic.Security;
using NopSolutions.NopCommerce.BusinessLogic.Shipping;
using NopSolutions.NopCommerce.BusinessLogic.Tax;
using NopSolutions.NopCommerce.Common;
using NopSolutions.NopCommerce.Common.Extensions;
using NopSolutions.NopCommerce.Common.Utils;
using NopSolutions.NopCommerce.Common.Utils.Html;
namespace NopSolutions.NopCommerce.BusinessLogic.SEO.Sitemaps
{
/// <summary>
/// Sitemap service
/// </summary>
public partial class SiteMapService : ISiteMapService
{
#region Fields
/// <summary>
/// Object context
/// </summary>
private readonly NopObjectContext _context;
/// <summary>
/// Cache service
/// </summary>
private readonly ICacheManager _cacheManager;
#endregion Fields
#region Ctor
/// <summary>
/// Ctor
/// </summary>
/// <param name="context">Object context</param>
public SiteMapService(NopObjectContext context)
{
this._context = context;
this._cacheManager = new NopRequestCache();
}
#endregion Ctor
#region Utilities
#endregion Utilities
#region Methods
/// <summary>
/// Gets a sitemap entry
/// </summary>
/// <param name="url">The url of the sitempa item</param>
/// <returns>Order</returns>
public Sitemap GetSitemap(string url)
{
if (!url.IsNotNullOrEmpty())
return null;
var query = from sm in _context.Sitemaps
where sm.PageUrl.Contains(url)
select sm;
var sitemap = query.SingleOrDefault();
return sitemap;
}
public Sitemap GetLastAddedSitemap(string pageType)
{
var query = (from sm in _context.Sitemaps
where sm.PageType.Equals(pageType)
orderby sm.UpdatedOn descending
select sm).Take(1);
var sitemap = query.SingleOrDefault();
return sitemap;
}
public List<Sitemap> GetInitEntries(string pageType)
{
var query = (from sm in _context.Sitemaps
where sm.PageType.Equals(pageType)
&& sm.Frequency == "Init"
select sm).Take(500);
return query.ToList();
}
/// <summary>
/// Bool to check if a given type has any entries in the init state.
/// </summary>
/// <param name="pageType">Page Type</param>
/// <returns>True or False</returns>
public bool IsInit(string pageType)
{
var query = (from sm in _context.Sitemaps
where sm.PageType.Equals(pageType)
&& sm.PageUrl.Equals("Init")
select sm).Take(1);
if (query == null)
return true;
else
return false;
}
public void InitSitemap(string pageType, int startProdId = 0)
{
_context.Sp_SitemapInit(pageType, startProdId);
}
/// <summary>
/// Inserts a sitemap entry
/// </summary>
/// <param name="sm">Sitemap Entry to Insert</param>
public void InsertSitemap(Sitemap sm)
{
if (sm == null)
throw new ArgumentNullException("sitemap");
sm.PageUrl = CommonHelper.EnsureNotNull(sm.PageUrl);
sm.PageType = CommonHelper.EnsureNotNull(sm.PageType);
sm.Frequency = CommonHelper.EnsureNotNull(sm.Frequency);
_context.Sitemaps.AddObject(sm);
_context.SaveChanges();
}
/// <summary>
/// Updates a sitemap entry
/// </summary>
/// <param name="sm">Sitemap Entry to update</param>
public void UpdateSitemap(Sitemap sm)
{
if (sm == null)
throw new ArgumentNullException("sitemap");
sm.PageUrl = CommonHelper.EnsureNotNull(sm.PageUrl);
sm.PageType = CommonHelper.EnsureNotNull(sm.PageType);
sm.Frequency = CommonHelper.EnsureNotNull(sm.Frequency);
if(_context.Sitemaps.Any(s => (s.PageUrl == sm.PageUrl || s.EntityId == sm.EntityId)))
{
if (!_context.IsAttached(sm))
_context.Sitemaps.Attach(sm);
}
else
{
_context.Sitemaps.AddObject(sm);
}
_context.SaveChanges();
}
#endregion Methods
#region Properties
/// <summary>
/// Gets a value indicating whether cache is enabled
/// </summary>
public bool CacheEnabled
{
get
{
return IoC.Resolve<ISettingManager>().GetSettingValueBoolean("Cache.OrderManager.CacheEnabled");
}
}
#endregion Properties
}
}
When I call
var sitemapservice = IoC.Resolve<ISiteMapService>();
sitemapservice.InitSitemap("Artist");
var smEntries = sitemapservice.GetInitEntries("Artist");
foreach (Sitemap sm in smEntries)
{
using (MvcMiniProfiler.MiniProfiler.StepStatic("Processing Entry: " + sm.EntityId + "," + sm.PageType))
{
sm.Frequency = "Monthly";
sm.PageUrl = SEOHelper.GetUrl(this.ArtistService.GetArtistById(sm.EntityId), this.SettingManager.StoreUrl);
using (MvcMiniProfiler.MiniProfiler.StepStatic("Updating database"))
{
sitemapservice.UpdateSitemap(sm);
}
curCount++;
}
}
It is supposed to get all the entries that are in the init stage and set them up with the correct url, but for whatever reason I keep getting errors saying An object with the same key already exists in the ObjectStateManager. Though the strange thing is the DB seems to be updating properly it just keeps giving me this error.
I think this is what you are referring to...
public class CollectionItem
{
public int CollectionId { get; set; }
public int ItemId { get; set; }
[RelatedTo(ForeignKey = "CollectionId")]
public Collection Collection { get; set; }
[RelatedTo(ForeignKey = "ItemId")]
public Item Item { get; set; }
}
And use model builder like so
var builder = new ModelBuilder();
// ...
builder.Entity<CollectionItem>().HasKey(p=>new {p.CollectionId, p.ItemId});
// ...
model = builder.CreateModel();
Hope this helps, I have tried to keep the names generic

How can I add errors to ModelState using the correct key in ASP.NET MVC?

I want to perform some simple form validation in my controller.
Here's an excerpt from the controller action:
// other code
if (!string.IsNullOrEmpty(editModel.NewPassword)
&& editModel.RepeatNewPassword != editModel.NewPassword) {
// problem line... How to I get the key from editModel?
ModelState.AddModelError("", "The new password does not match the repeated password.")
}
// other code
It appears that must use a string as the error's key. Is there a way i can generate the corect key from the model, or should I just check for what input name Html.PasswordFor(x => x.NewPassword) returns?
Or in your ViewModel you can do this
public class ClassName
{
public string Password { get; set; }
[Compare("Password" , "Password Must Match")]
public string ConfirmPassword { get; set; }
}
this is new to mvc3 and you can implement your custom attribute like this fairly easy in mvc3
because IsValid now recives a ValidationContext parameter which contains information about the validation that is being performed like the type of the model and metadata associated with it so you can use reflection to get other properties and their value the CompareAttribute made use of this feature
Not exactly what you are asking for, but will solve your problem:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
#region [ Fields ]
/// <summary>
/// Defines the default error messsage
/// </summary>
private const string DefaultErrorMessage = "'{0}' and '{1}' do not match.";
/// <summary>
/// Defines a typeId
/// </summary>
private readonly object typeId = new object();
#endregion
#region [ Constructors ]
/// <summary>
/// Initializes a new instance of the PropertiesMustMatchAttribute class.
/// </summary>
/// <param name="originalProperty">The original property name</param>
/// <param name="confirmProperty">The confirm (or match) property name</param>
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(DefaultErrorMessage)
{
this.OriginalProperty = originalProperty;
this.ConfirmProperty = confirmProperty;
}
#endregion
#region [ Properties ]
/// <summary>
/// Gets the confirm property name
/// </summary>
public string ConfirmProperty { get; private set; }
/// <summary>
/// Gets the original property name
/// </summary>
public string OriginalProperty { get; private set; }
/// <summary>
/// Gets a unique identifier for this <see cref="T:System.Attribute"/>.
/// </summary>
/// <returns>An <see cref="T:System.Object"/> that is a unique identifier for the attribute.</returns>
/// <filterpriority>2</filterpriority>
public override object TypeId
{
get
{
return this.typeId;
}
}
#endregion
#region [ Overrides ]
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <returns>An instance of the formatted error message.</returns>
/// <param name="name">The name to include in the formatted message.</param>
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, this.OriginalProperty, this.ConfirmProperty);
}
/// <summary>
/// Determines whether the specified value of the object is valid.
/// </summary>
/// <returns>true if the specified value is valid; otherwise, false.</returns>
/// <param name="value">The value of the object to validate. </param>
public override bool IsValid(object value)
{
var properties = TypeDescriptor.GetProperties(value);
var originalValue = properties.Find(this.OriginalProperty, true /* ignoreCase */).GetValue(value);
var confirmValue = properties.Find(this.ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Equals(originalValue, confirmValue);
}
#endregion
}
And then:
[PropertiesMustMatch("NewPassword", "RepeatNewPassword ", ErrorMessage = "The new password and confirmation password do not match.")]
public class YourModel
{
public string NewPassword {get;set;}
public string RepeatNewPassword {get;set;}
}

Custom Authorize Attribute additional Param?

im looking for a way to customize my Authorize Attribute so that i can implement it correctly with my own MembershipProvider.
What i need is to have the IsInRoles(string role, int perm) for example, in other word, i want to have it replace with a new IsinRoles or maybe create another method to archive this result.
Is it possible? or i need to write a different authorize attribute?
Thank you very much for your concern...
PS: im working on ASP.net MVC, so i need to have the [Authorize] filter up and running.
I think you can just add a public property to your custom AuthorizeAttribute.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public YourCustomRoles RequiredRole;
public int YourCustomValue;
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null) throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (httpContext.User.Identity.IsAuthenticated == false) return false;
// Can use your properties if needed and do your checks
bool authorized = DoSomeCustomChecksHere();
return authorized;
}
}
Usage I think would be (haven't tried it though):
[CustomAuthorizeAttribute (RequiredRole=MyCustomRole.Role1 | MyCustomRole.Role2, YourCustomValue=1234)]

Resources