Reduce IdentityServer4 access_token length by removing claims in profile service - asp.net

I am working on IdentityServer4 application. The generated Access_token length is becoming too lengthy due to the claim value added to the context.IssuedClaims. I tried to remove claim type called "entity" and it helps in reducing the access_token length. But its removing the claim from ClaimsPrincipal as well. Is there a way I can add this claim back to my ClaimsPrincipal so that I can access it from all my client applications? Currently I am invoking a separate API to get the claim back every time. Below is the code from my ProfileService where I am filtering the claim.
public async Task GetProfileDataAsync(IdentityServer4.Models.ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
var principal = await _claimsFactory.CreateAsync(user);
//Retrieve all the claims associated with the user
var claims = from claimsdata in principal.Claims select new System.Security.Claims.Claim(claimsdata.Type, claimsdata.Value);
//Exclude claim type "entity" since its huge in numbers and causing access_token size to grow
var claimsWithoutEntity = claims.Where(x => x.Type != "entity");
context.IssuedClaims.AddRange(claimsWithoutEntity);
var roleClaims = _roleService.GetRoleClaims(user);
context.IssuedClaims.AddRange(roleClaims);
}

A different alternative to reduce the cookie size without modifying the access token is to create a SessionStore, and you can set it using the SessionStore parameter here:
}).AddCookie(options =>
{
...
options.SessionStore = new MySessionStore();
})
What does a SessionStore do?
See this picture, taken from one of my training classes:
Here's a sample in-memory store
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Serilog;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace SessionStore
{
/// <summary>
/// MySessionStore
///
/// Custom session store, to hold the tokens in memory instead of storing them inside the Cookie.
///
/// This provides an abstract storage mechanic to preserve identity information on the server while
/// only sending a simple identifier key to the client. This is most commonly used to mitigate issues
/// with serializing large identities into cookies.
///
/// TODO:
/// - Needs logic to remove older items, otherwise we might run out of memory here.
/// - Use the MemoryCache instead of a dictionary?
///
/// Written by Tore Nestenius to be used in the IdentityServer in production training class.
/// https://www.tn-data.se
///
/// </summary>
internal class MySessionStore : ITicketStore
{
private readonly Serilog.ILogger _logger;
private readonly ConcurrentDictionary<string, AuthenticationTicket> mytickets = new();
public MySessionStore()
{
_logger = Log.Logger;
}
/// <summary>
/// Remove the identity associated with the given key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public Task RemoveAsync(string key)
{
_logger.Debug("MySessionStore.RemoveAsync Key=" + key);
if (mytickets.ContainsKey(key))
{
mytickets.TryRemove(key, out _);
}
return Task.FromResult(0);
}
/// <summary>
/// Tells the store that the given identity should be updated.
/// </summary>
/// <param name="key"></param>
/// <param name="ticket"></param>
/// <returns></returns>
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
_logger.Debug("MySessionStore.RenewAsync Key=" + key + ", ticket = " + ticket.AuthenticationScheme);
mytickets[key] = ticket;
return Task.FromResult(false);
}
/// <summary>
/// Retrieves an identity from the store for the given key.
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
_logger.Error("MySessionStore.RetrieveAsync Key=" + key);
if (mytickets.ContainsKey(key))
{
var ticket = mytickets[key];
return Task.FromResult(ticket);
}
else
{
return Task.FromResult((AuthenticationTicket)null!);
}
}
/// <summary>
/// Store the identity ticket and return the associated key.
/// </summary>
/// <param name="ticket"></param>
/// <returns></returns>
public Task<string> StoreAsync(AuthenticationTicket ticket)
{
//Only add one at the time to avoid race conditions
lock(this)
{
//Make sure the key is does not already exist in the dictionary
bool result = false;
string key;
do
{
key = Guid.NewGuid().ToString();
result = mytickets.TryAdd(key, ticket);
} while (result == false);
string username = ticket?.Principal?.Identity?.Name ?? "Unknown";
_logger.Debug("MySessionStore.StoreAsync ticket=" + username + ", key=" + key);
return Task.FromResult(key);
}
}
}
}

Related

Protobuf-net v3 DateTimeOffset Surrogate

I've seen some other SO's where people are using DateTimeOffset surrogates to handle deserializing those properties, however when I try to copy those, I continue to get a System.InvalidOperationException: No serializer defined for type: System.DateTimeOffset error.
[ProtoContract]
public TestClass
{
[ProtoMember(1)]
public DateTimeOffset Time { get; set; }
}
Surrogate class
[ProtoContract]
public class DateTimeOffsetSurrogate
{
[ProtoMember(1)]
public long DateTimeTicks { get; set; }
[ProtoMember(2)]
public short OffsetMinutes { get; set; }
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset value)
{
return new DateTimeOffsetSurrogate
{
DateTimeTicks = value.Ticks,
OffsetMinutes = (short)value.Offset.TotalMinutes
};
}
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate value)
{
return new DateTimeOffset(value.DateTimeTicks, TimeSpan.FromMinutes(value.OffsetMinutes));
}
}
Then I'm registering it right before the http call. I've tried moving this registration into a few different places but it doesn't seem to make a difference. Did this change in v3 or something or am I doing something wrong? Sorry - new to protobuf-net :)
public async Task<Response<IEnumerable<TestClass>>> GetData()
{
RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
var request = new HttpRequestMessage(HttpMethod.Get, "my-url");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
var result = await _httpClient.SendAsync(request);
var items= ProtoBuf.Serializer.Deserialize<Response<IEnumerable<TestClass>>>(await result.Content.ReadAsStreamAsync());
return items;
}
I am using version 3.1.22 (Currently the latest) of protobuf-net right now with the following setup.
// Needs to be only called at application startup, e.g. in the Startup.cs calls of your web API.
RuntimeTypeModel.Default.Add(typeof(DateTimeOffset), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
RuntimeTypeModel.Default.Add(typeof(DateTimeOffset?), false).SetSurrogate(typeof(DateTimeOffsetSurrogate));
The surrogate handler I use successfully, can be found below. No black magic happening here, just serializing / deserializing the unix timestamp to long and vice versa:
namespace Something
{
using System;
using ProtoBuf;
/// <summary>
/// A surrogate handler for the <see cref="DateTimeOffset"/> class.
/// </summary>
[ProtoContract(Name = nameof(DateTimeOffset))]
public class DateTimeOffsetSurrogate
{
/// <summary>
/// Gets or sets the value.
/// </summary>
[ProtoMember(1)]
public long? Value { get; set; }
/// <summary>
/// Converts the <see cref="DateTimeOffsetSurrogate"/> to a <see cref="DateTimeOffset"/>.
/// </summary>
/// <param name="surrogate">The surrogate handler.</param>
public static implicit operator DateTimeOffset(DateTimeOffsetSurrogate surrogate)
{
if (surrogate?.Value is null)
{
throw new ArgumentNullException(nameof(surrogate));
}
var dt = DateTimeOffset.FromUnixTimeMilliseconds(surrogate.Value.Value);
dt = dt.ToLocalTime();
return dt;
}
/// <summary>
/// Converts the <see cref="DateTimeOffsetSurrogate"/> to a <see cref="DateTimeOffset"/>.
/// </summary>
/// <param name="surrogate">The surrogate handler.</param>
public static implicit operator DateTimeOffset?(DateTimeOffsetSurrogate? surrogate)
{
if (surrogate?.Value is null)
{
return null;
}
var dt = DateTimeOffset.FromUnixTimeMilliseconds(surrogate.Value.Value);
dt = dt.ToLocalTime();
return dt;
}
/// <summary>
/// Converts the <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffsetSurrogate"/>.
/// </summary>
/// <param name="source">The source.</param>
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset source)
{
return new DateTimeOffsetSurrogate
{
Value = source.ToUnixTimeMilliseconds()
};
}
/// <summary>
/// Converts the <see cref="DateTimeOffset"/> to a <see cref="DateTimeOffsetSurrogate"/>.
/// </summary>
/// <param name="source">The source.</param>
public static implicit operator DateTimeOffsetSurrogate(DateTimeOffset? source)
{
return new DateTimeOffsetSurrogate
{
Value = source?.ToUnixTimeMilliseconds()
};
}
}
}
Maybe, you want to give it a try. Maybe [ProtoContract(Name = nameof(DateTimeOffset))] is what you're missing, but I'm not sure.

Web APIs methods with Warnings

I have this method in an API that does POST for creating a record, but before inserting that record in DB there are some validations that i must do, I might come back with warnings and I need to return back these warnings to the client to confirm back.
what is the best way to do that in Web API's? or should i split the method into 2, one for validation and one for saving?
Set up your Web Api the following way.
[HttpPost]
public IHttpActionResult DoStuff(object item)
{
if(!validate(item))
{
return this.BadRequest("Validation failed blablabla");
}
else
{
//insert logic
}
return this.Ok();
}
What happens is you validate the object you send to the API. When it fails to validate you return the request was not correct, with the message you specified.
When the validation succeeds the insert logic is called and you return a OK result when it succeeds.
I create and use a wrapper which would contain object to be bound to ui, messages (error or validation or warning) and total.
/// <summary>
/// Class for the Repository response object
/// </summary>
/// <typeparam name="T"></typeparam>
public class CLSResponse<T>
{
/// <summary>
/// Gets or sets the messages to be returned in the response.
/// </summary>
/// <value>The messages.</value>
public IEnumerable<KeyValuePair<string, string>> Messages {
get { return m_Messages; }
set { m_Messages = value; }
}
private IEnumerable<KeyValuePair<string, string>> m_Messages;
/// <summary>
/// Gets or sets the service model to be returned in the response.
/// </summary>
/// <value>The service model.</value>
public T ServiceModel {
get { return m_ServiceModel; }
set { m_ServiceModel = value; }
}
private T m_ServiceModel;
/// <summary>
/// Gets or sets the totalitems.
/// </summary>
/// <value>The TotalItems.</value>
public int TotalItems {
get { return m_TotalItems; }
set { m_TotalItems = value; }
}
private int m_TotalItems;
/// <summary>
/// Gets and Sets the Message Type based on the MessageType Struct
/// </summary>
public string MessagesType;
}
/// <summary>
/// Struct for MessageTypes to be returned with messages, in the response object
/// </summary>
public struct MessagesType
{
/// <summary>
/// Validation
/// </summary>
public static string Validation = "Validation";
/// <summary>
/// Warning
/// </summary>
public static string Warning = "Warning";
/// <summary>
/// Error
/// </summary>
public static string Error = "Error";
/// <summary>
/// Unauthorized
/// </summary>
public static string UnAuthorized = "Unauthorized";
}
Then in your repository layer or logic layer
public CLSResponse<bool> CreateUser(string request)
{
var messages = new List<KeyValuePair<string, string>>();
try
{
//Do something
if (!validation)
{
messages.Add(MessagesType.Validation, "Invalid");
return new CLSResponse<bool> {
ServiceModel = false,
Messages = messages,
MessagesType = MessagesType.Validation
};
}
else {
return new CLSResponse<bool> {
ServiceModel = true,
Messages = messages,
MessagesType = MessagesType.Error
};
}
}
catch (Exception ex)
{
messages.Add(MessagesType.Error, "UpdateFailed");
return new CLSResponse<bool> {
ServiceModel = false,
Messages = messages,
MessagesType = MessagesType.Error
};
}
}
Now on controller,
[HttpPost]
public HttpResponseMessage<CLSResponse<bool>> CreateUser(string input)
{
var res = LogicLayer.CreateUser(input);
//Check for res.MessageType and set the status code
if (res.MessagesType = MessagesType.Validation)
{
return Request.CreateResponse<CLSResponse<bool>>(HttpStatusCode.PreconditionFailed, res);
}
}
This way your response object still has the warning you added in logic layer and depending on the status code returned, you can handle those messages on client side.

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

signalR OnDisconnected() cancel task

When a page loads I start a new task in my hub. This task sends data to a browser updating a certain html elements. When I browse away from the page I want to stop the task.
The problem is that before the task is stopped (due to it's sleep argument), a new tokenSource = new CancellationTokenSource();
is set before the previous instance of this task had a chance to stop.
What i'm trying to do is to have the task stop when browsing away from the page to a different page that is not requiring signalR. BUT maybe, not stop it if it's only a refresh of the same page. Not sure how to do it. To sum it up, I want to guarantee that only 1 instance of this task is running (AND only on the page that requires it/or have a listener)
any info greatly appreciated.
thanks
CODE:
public class TaskActionStatus : Hub
{
#region Static Fields
/// <summary>
/// The token source.
/// </summary>
private static CancellationTokenSource tokenSource;
/// <summary>
/// The url string.
/// </summary>
private static string url = string.Empty;
#endregion
#region Public Methods and Operators
/// <summary>
/// The get tasks status.
/// </summary>
/// <param name="siteId">
/// The site id.
/// </param>
/// <param name="location"></param>
public void GetTasksStatus(int? siteId)
{
var taskRepository = UnityContainerSetup.Container.Resolve<ITaskRepository>();
tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
// init task for checking task statuses
var tasksItem = new DownloadTaskItem();
// start task only if at least one listener
if (UserHandler.ConnectedIds.Count < 2 && !taskRepository.IsTasksStatusAsyncRunning())
{
taskRepository.GetTasksStatusAsync(siteId, tasksItem, ct);
// subscribe to event [ listener ]
tasksItem.Changed += this.UpdateTasksStatus;
}
else tokenSource.Cancel();
}
/// <summary>
/// The on connected.
/// </summary>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(this.Context.ConnectionId);
return base.OnConnected();
}
/// <summary>
/// The on disconnected.
/// </summary>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public override Task OnDisconnected()
{
UserHandler.ConnectedIds.Remove(this.Context.ConnectionId);
if (UserHandler.ConnectedIds.Count == 0)
{
try
{
tokenSource.Cancel();
}
catch (Exception ex)
{
Log.Error(ex);
}
}
return base.OnDisconnected();
}
/// <summary>
/// The update tasks status.
/// </summary>
/// <param name="sender">
/// The sender.
/// </param>
/// <param name="e">
/// The e.
/// </param>
public void UpdateTasksStatus(object sender, TaskEventArgs e)
{
this.Clients.All.updateMessages(e.Tasks);
}
#endregion
}
/// <summary>
/// The user handler.
/// </summary>
public static class UserHandler
{
#region Static Fields
/// <summary>
/// The connected ids.
/// </summary>
public static HashSet<string> ConnectedIds = new HashSet<string>();
#endregion
}
public bool IsTasksStatusAsyncRunning()
{
if (tasksStatusAsync != null && tasksStatusAsync.Status.Equals(TaskStatus.Running))
{
return true;
}
return false;
}
Moving this line:
tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
...
making it this:
if (UserHandler.ConnectedIds.Count < 2)
{
Trace.WriteLine("GetTasksStatus: Starting new task");
tokenSource = new CancellationTokenSource();
CancellationToken ct = tokenSource.Token;
taskRepository.GetTasksStatusAsync(siteId, tasksItem, ct);
// subscribe to event [ listener ]
tasksItem.Changed += this.UpdateTasksStatus;
}
did it for me. thanks

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;}
}

Resources