Caching & Compressing Static Content in ASP.NET MVC - asp.net-mvc

I am using Google Page Speed in Firefox to optimize my site seed. It's an ASP.NET MVC site that I'm running using the Visual Studio dev server. I have static content located in the /Content folder (images, scripts, styles). Google Page Speed suggests that I implement caching b/c no expiration is specified for my static content. I included the following in web.config but it doesn't seem to help:
<system.webServer>
<staticContent>
<clientCache cacheControlMaxAge="7.00:00:00" cacheControlMode="UseMaxAge"/>
</staticContent>
</system.webServer>
For that matter, I also want to enable compression of these files. Would love to know how to do both. Thanks.

For compression, I use the following custom CompressAttribute class so that you only have to decorate controller methods with [Compress] when you want to enable compression for it:
public class CompressAttribute : ActionFilterAttribute
{
/// <summary>
/// Enables compression on page response
/// </summary>
/// <param name="filterContext">Filter context</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding)) return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("DEFLATE"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new WebCompressionStream(response.Filter, CompressionType.Deflate);
}
else if (acceptEncoding.Contains("GZIP"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new WebCompressionStream(response.Filter, CompressionType.GZip);
}
}
}
The WebCompressionStream class looks like this:
public sealed class WebCompressionStream : Stream
{
private readonly Stream _compSink;
private readonly Stream _finalSink;
/// <summary>
/// Initializes a new instance of the <see cref="WebCompressionStream"/> class.
/// </summary>
/// <param name="stm">The stream</param>
/// <param name="comp">The compression type to use</param>
public WebCompressionStream(Stream stm, CompressionType comp)
{
switch (comp)
{
case CompressionType.Deflate:
_compSink = new DeflateStream((_finalSink = stm), CompressionMode.Compress);
break;
case CompressionType.GZip:
_compSink = new GZipStream((_finalSink = stm), CompressionMode.Compress);
break;
default:
throw new ArgumentException();
}
}
/// <summary>
/// Gets the sink.
/// </summary>
/// <value>The sink.</value>
public Stream Sink
{
get
{
return _finalSink;
}
}
/// <summary>
/// Gets the type of the compression.
/// </summary>
/// <value>The type of the compression.</value>
public CompressionType CompressionType
{
get
{
return _compSink is DeflateStream ? CompressionType.Deflate : CompressionType.GZip;
}
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
/// <value></value>
/// <returns>true if the stream supports reading; otherwise, false.</returns>
public override bool CanRead
{
get
{
return false;
}
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
/// <value></value>
/// <returns>true if the stream supports seeking; otherwise, false.</returns>
public override bool CanSeek
{
get
{
return false;
}
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
/// <value></value>
/// <returns>true if the stream supports writing; otherwise, false.</returns>
public override bool CanWrite
{
get
{
return true;
}
}
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
/// <value></value>
/// <returns>A long value representing the length of the stream in bytes.</returns>
/// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override long Length
{
get
{
throw new NotSupportedException();
}
}
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
/// <value></value>
/// <returns>The current position within the stream.</returns>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
public override void Flush()
{
//We do not flush the compression stream. At best this does nothing, at worst it
//loses a few bytes. We do however flush the underlying stream to send bytes down the
//wire.
_finalSink.Flush();
}
/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"/> indicating the reference point used to obtain the new position.</param>
/// <returns>
/// The new position within the current stream.
/// </returns>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
/// </returns>
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is larger than the buffer length. </exception>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer"/> is null. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="offset"/> or <paramref name="count"/> is negative. </exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
/// <exception cref="T:System.ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length. </exception>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer"/> is null. </exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="offset"/> or <paramref name="count"/> is negative. </exception>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override void Write(byte[] buffer, int offset, int count)
{
_compSink.Write(buffer, offset, count);
}
/// <summary>
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
/// </summary>
/// <param name="value">The byte to write to the stream.</param>
/// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
/// <exception cref="T:System.NotSupportedException">The stream does not support writing, or the stream is already closed. </exception>
/// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
public override void WriteByte(byte value)
{
_compSink.WriteByte(value);
}
/// <summary>
/// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream.
/// </summary>
public override void Close()
{
_compSink.Close();
_finalSink.Close();
base.Close();
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_compSink.Dispose();
_finalSink.Dispose();
}
base.Dispose(disposing);
}
}
/// <summary>
/// Specifies the compression type to be used
/// </summary>
public enum CompressionType
{
/// <summary>
/// Compression will use deflate
/// </summary>
Deflate,
/// <summary>
/// Compression will use GZip
/// </summary>
GZip
}
USAGE:
[Compress]
public ActionResult SomeView()
{
return View("SomeView");
}

Compression is best to be set directly on IIS if you can. Otherwise, using custom attributes for action result compression is widely spread practice.
For javascript, css and resource files you can use tools like Ajax Minifier (it can be also set as build task).
For packaging all javascript or css files together you could create a controller action that combines them and than just call it in your view:
Also, when using common javascript libraries, such as jQuery, consider using any of CDN providers.

Related

How to process video? which I am getting from the team-recording bot

My Team-recording bot code is running in azure Kubernetes service. The code is written in the .netframework language. The bot can join the team meeting and it is recording the meeting and saving the files in audio(wav) format to local in a .zip file. I want to process the video files to local in a .zip file. Right now we have AudioProcessor class to get the audio files:` public BotMediaStream(
ILocalMediaSession mediaSession,
string callId,
IGraphLogger logger,
IEventPublisher eventPublisher,
IAzureSettings settings
)
: base(logger)
{
ArgumentVerifier.ThrowOnNullArgument(mediaSession, nameof(mediaSession));
ArgumentVerifier.ThrowOnNullArgument(logger, nameof(logger));
ArgumentVerifier.ThrowOnNullArgument(settings, nameof(settings));
this.participants = new List<IParticipant>();
_eventPublisher = eventPublisher;
_callId = callId;
_mediaStream = new MediaStream(
settings,
logger,
mediaSession.MediaSessionId.ToString()
);
// Subscribe to the audio media.
this._audioSocket = mediaSession.AudioSocket;
if (this._audioSocket == null)
{
throw new InvalidOperationException("A mediaSession needs to have at least an audioSocket");
}
this._audioSocket.AudioMediaReceived += this.OnAudioMediaReceived;
this.videoSockets = mediaSession.VideoSockets?.ToList();
if (this.videoSockets?.Any() == true)
{
this.videoSockets.ForEach(videoSocket => videoSocket.VideoMediaReceived += this.OnVideoMediaReceived);
}
// Subscribe to the VBSS media.
this.vbssSocket = mediaSession.VbssSocket;
if (this.vbssSocket != null)
{
mediaSession.VbssSocket.VideoMediaReceived += this.OnVbssMediaReceived;
}
}
`
private ILocalMediaSession CreateLocalMediaSession(Guid mediaSessionId = default)
{
try
{
// create media session object, this is needed to establish call connections
var videoSocketSettings = new List<VideoSocketSettings>();
// create the receive only sockets settings for the multiview support
for (int i = 0; i < SampleConstants.NumberOfMultiviewSockets; i++)
{
videoSocketSettings.Add(new VideoSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
ReceiveColorFormat = VideoColorFormat.H264,
});
}
// Create the VBSS socket settings
var vbssSocketSettings = new VideoSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
ReceiveColorFormat = VideoColorFormat.H264,
MediaType = MediaType.Vbss,
SupportedSendVideoFormats = new List<VideoFormat>
{
// fps 1.875 is required for h264 in vbss scenario.
VideoFormat.H264_1920x1080_1_875Fps,
},
};
// create media session object, this is needed to establish call connections
var mediaSession = this.Client.CreateMediaSession(
new AudioSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
SupportedAudioFormat = AudioFormat.Pcm16K,
},
videoSocketSettings,
vbssSocketSettings,
mediaSessionId: mediaSessionId);
return mediaSession;
}
catch (Exception e)
{
_logger.Log(System.Diagnostics.TraceLevel.Error, e.Message);
throw;
}
}
Here audio is getting processed from team-recording-bot
AudioProcessor code: `// ***********************************************************************
// Assembly : RecordingBot.Services
// Author : JasonTheDeveloper
// Created : 09-07-2020
//
// Last Modified By : dannygar
// Last Modified On : 09-07-2020
// ***********************************************************************
// <copyright file="AudioProcessor.cs" company="Microsoft">
// Copyright © 2020
// </copyright>
// <summary></summary>
// ***********************************************************************
using NAudio.Wave;
using RecordingBot.Model.Constants;
using RecordingBot.Services.Contract;
using RecordingBot.Services.ServiceSetup;
using RecordingBot.Services.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace RecordingBot.Services.Media
{
/// <summary>
/// Class AudioProcessor.
/// Implements the <see cref="RecordingBot.Services.Util.BufferBase{RecordingBot.Services.Media.SerializableAudioMediaBuffer}" />
/// </summary>
/// <seealso cref="RecordingBot.Services.Util.BufferBase{RecordingBot.Services.Media.SerializableAudioMediaBuffer}" />
public class AudioProcessor : BufferBase<SerializableAudioMediaBuffer>
{
/// <summary>
/// The writers
/// </summary>
readonly Dictionary<string, WaveFileWriter> _writers = new Dictionary<string, WaveFileWriter>();
/// <summary>
/// The processor identifier
/// </summary>
private readonly string _processorId = null;
/// <summary>
/// The settings
/// </summary>
private readonly AzureSettings _settings;
/// <summary>
/// Initializes a new instance of the <see cref="AudioProcessor" /> class.
/// </summary>
/// <param name="settings">The settings.</param>
public AudioProcessor(IAzureSettings settings)
{
_processorId = Guid.NewGuid().ToString();
_settings = (AzureSettings)settings;
}
/// <summary>
/// Processes the specified data.
/// </summary>
/// <param name="data">The data.</param>
protected override async Task Process(SerializableAudioMediaBuffer data)
{
if (data.Timestamp == 0)
{
return;
}
var path = Path.Combine(Path.GetTempPath(), BotConstants.DefaultOutputFolder, _settings.MediaFolder, _processorId);
// First, write all audio buffer, unless the data.IsSilence is checked for true, into the all speakers buffer
var all = "all";
var all_writer = _writers.ContainsKey(all) ? _writers[all] : InitialiseWavFileWriter(path, all);
if (data.Buffer != null)
{
// Buffers are saved to disk even when there is silence.
// If you do not want this to happen, check if data.IsSilence == true.
await all_writer.WriteAsync(data.Buffer, 0, data.Buffer.Length).ConfigureAwait(false);
}
if (data.SerializableUnmixedAudioBuffers != null)
{
foreach (var s in data.SerializableUnmixedAudioBuffers)
{
if (string.IsNullOrWhiteSpace(s.AdId) || string.IsNullOrWhiteSpace(s.DisplayName))
{
continue;
}
var id = s.AdId;
var writer = _writers.ContainsKey(id) ? _writers[id] : InitialiseWavFileWriter(path, id);
// Write audio buffer into the WAV file for individual speaker
await writer.WriteAsync(s.Buffer, 0, s.Buffer.Length).ConfigureAwait(false);
// Write audio buffer into the WAV file for all speakers
await all_writer.WriteAsync(s.Buffer, 0, s.Buffer.Length).ConfigureAwait(false);
}
}
}
/// <summary>
/// Initialises the wav file writer.
/// </summary>
/// <param name="rootFolder">The root folder.</param>
/// <param name="id">The identifier.</param>
/// <returns>WavFileWriter.</returns>
private WaveFileWriter InitialiseWavFileWriter(string rootFolder, string id)
{
var path = AudioFileUtils.CreateFilePath(rootFolder, $"{id}.wav");
// Initialize the Wave Format using the default PCM 16bit 16K supported by Teams audio settings
var writer = new WaveFileWriter(path, new WaveFormat(
rate: AudioConstants.DefaultSampleRate,
bits: AudioConstants.DefaultBits,
channels: AudioConstants.DefaultChannels));
_writers.Add(id, writer);
return writer;
}
/// <summary>
/// Finalises the wav writing and returns a list of all the files created
/// </summary>
/// <returns>System.String.</returns>
public async Task<string> Finalise()
{
//drain the un-processed buffers on this object
while (Buffer.Count > 0)
{
await Task.Delay(200);
}
var archiveFile = Path.Combine(Path.GetTempPath(), BotConstants.DefaultOutputFolder, _settings.MediaFolder, _processorId, $"{Guid.NewGuid()}.zip");
try
{
using (var stream = File.OpenWrite(archiveFile))
{
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create))
{
// drain all the writers
foreach (var writer in _writers.Values)
{
var localFiles = new List<string>();
var localArchive = archive; //protect the closure below
var localFileName = writer.Filename;
localFiles.Add(writer.Filename);
await writer.FlushAsync();
writer.Dispose();
// Is Resampling and/or mono to stereo conversion required?
if (_settings.AudioSettings.WavSettings != null)
{
// The resampling is required
localFiles.Add(AudioFileUtils.ResampleAudio(localFileName, _settings.AudioSettings.WavSettings, _settings.IsStereo));
}
else if (_settings.IsStereo) // Is Stereo audio required?
{
// Convert mono WAV to stereo
localFiles.Add(AudioFileUtils.ConvertToStereo(localFileName));
}
// Remove temporary saved local WAV file from the disk
foreach (var localFile in localFiles)
{
await Task.Run(() =>
{
var fInfo = new FileInfo(localFile);
localArchive.CreateEntryFromFile(localFile, fInfo.Name, CompressionLevel.Optimal);
File.Delete(localFile);
}).ConfigureAwait(false);
}
}
}
}
}
finally
{
await End();
}
return archiveFile;
}
}
}
`
I request you to please help us on this. We need to get video from team-recording-bot and save it to local in.zip file and also to s3 bucket. Please, Please help us on this.

OpenISO8583.Net BCD fix format unpack error

I downloaded the code from code.google and get the last version v0.5.2
I set a field in bcd fix format,which is N-6 in bcd format(bit._003_proc_code)
For ex:
*Field definition:
DefaultTemplate =new Template
{
{ Bit._002_PAN, FieldDescriptor.BcdVar(2, 19,Formatters.Ascii) },
{ Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(3)},
{ Bit._004_TRAN_AMOUNT, FieldDescriptor.BcdFixed(6) },
..............
}
usage:
Iso8583 msg =new Iso8584();
msg[3]="000000";
when i unpack the message ,i can only get “0000” from message 3 .
is this a bug or error in definition
I would wait to hear from John Oxley to see if this is just a coding error. Now that I said that, I think it is a bug.
I had problems with the BcdFixed definition and ended up creating a new Fixed Length BCD formatter to work around the problem.
Here is what I did:
I created a class called FixedLengthBcdFormatter which is a variation of the FixedLengthFormatter class.
///
/// Fixed field formatter
///
public class FixedLengthBcdFormatter : ILengthFormatter
{
private readonly int _packedLength;
private readonly int _unPackedLength;
///<summary>
/// Fixed length field formatter
///</summary>
///<param name = "unPackedLength">The unpacked length of the field.</param>
public FixedLengthBcdFormatter(int unPackedLength)
{
_unPackedLength = unPackedLength;
double len = _unPackedLength;
_packedLength = (int)Math.Ceiling(len / 2);
}
#region ILengthFormatter Members
/// <summary>
/// Get the length of the packed length indicator. For fixed length fields this is 0
/// </summary>
public int LengthOfLengthIndicator
{
get { return 0; }
}
/// <summary>
/// The maximum length of the field displayed as a string for descriptors
/// </summary>
public string MaxLength
{
get { return _unPackedLength.ToString(); }
}
/// <summary>
/// Descriptor for the length formatter used in ToString methods
/// </summary>
public string Description
{
get { return "FixedBcd"; }
}
/// <summary>
/// Get the length of the field
/// </summary>
/// <param name = "msg">Byte array of message data</param>
/// <param name = "offset">offset to start parsing</param>
/// <returns>The length of the field</returns>
public int GetLengthOfField(byte[] msg, int offset)
{
return _unPackedLength;
}
/// <summary>
/// Pack the length header into the message
/// </summary>
/// <param name = "msg">Byte array of the message</param>
/// <param name = "length">The length to pack into the message</param>
/// <param name = "offset">Offset to start the packing</param>
/// <returns>offset for the start of the field</returns>
public int Pack(byte[] msg, int length, int offset)
{
return offset;
}
/// <summary>
/// Check the length of the field is valid
/// </summary>
/// <param name = "packedLength">the packed length of the field</param>
/// <returns>true if valid, false otherwise</returns>
public bool IsValidLength(int packedLength)
{
return packedLength == _packedLength;
}
#endregion
}
Modified the FieldDescription class / BcdFixed declaration
///
/// The bcd fixed.
///
///
/// The length.
///
///
///
public static IFieldDescriptor BcdFixed(int unpackedLength)
{
return Create(new FixedLengthBcdFormatter(unpackedLength), FieldValidators.N, Formatters.Bcd, null);
}
Then change your formatter declaration to provide the unpacked length as the paramter.
Bit._003_PROC_CODE, FieldDescriptor.BcdFixed(6)},
Again, all this may have been unnecessary because I didn't know something about the existing code, but it is working for me.
I hope this helps.

Where to find C# sample code to implement password recovery in ASP .NET MVC2

How to implement password reset in MVC2 application?
Passwords are hashed using ASP .NET membership provider. Password recovery question is not used. Standard ASP .NET MVC2 project template with standard AccountController class is used.
If user forgots password, email with temporary link or with new password should sent to user e-mail address .
Where to find code to implement this in MVC 2 C# ?
stack overflow contains two answers which discuss methods about implementing this. There is not sample code.
I googled for "asp .net mvc password reset c# sample code download" but havent found sample code for this.
I'm new to MVC. Where to find sample code for password recovery? This is missing from VS2010 generated project template.
Update
I tried this code in Mono 2.10 but got exception:
CspParameters not supported by Mono
at line
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
How to run it in Mono ?
Stack Trace:
System.NotSupportedException: CspParameters not supported by Mono
at System.Security.Cryptography.PasswordDeriveBytes.CryptDeriveKey (string,string,int,byte[]) [0x0001b] in /usr/src/redhat/BUILD/mono-2.10.2/mcs/class/corlib/System.Security.Cryptography/PasswordDeriveBytes.cs:197
at store2.Helpers.Password.EncodeMessageWithPassword (string,string) <IL 0x00055, 0x000f3>
at store2.Helpers.AccountHelper.GetTokenForValidation (string) <IL 0x00033, 0x00089>
at MvcMusicStore.Controllers.AccountController.PasswordReminder (MvcMusicStore.Models.PasswordReminderModel) <IL 0x001ac, 0x00495>
at (wrapper dynamic-method) System.Runtime.CompilerServices.ExecutionScope.lambda_method (System.Runtime.CompilerServices.ExecutionScope,System.Web.Mvc.ControllerBase,object[]) <IL 0x00020, 0x0005b>
at System.Web.Mvc.ActionMethodDispatcher.Execute (System.Web.Mvc.ControllerBase,object[]) <IL 0x00008, 0x0001b>
at System.Web.Mvc.ReflectedActionDescriptor.Execute (System.Web.Mvc.ControllerContext,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00072, 0x00103>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod (System.Web.Mvc.ControllerContext,System.Web.Mvc.ActionDescriptor,System.Collections.Generic.IDictionary`2<string, object>) <IL 0x00003, 0x00019>
at System.Web.Mvc.ControllerActionInvoker/<>c__DisplayClassd.<InvokeActionMethodWithFilters>b__a () <IL 0x0002d, 0x00068>
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter (System.Web.Mvc.IActionFilter,System.Web.Mvc.ActionExecutingContext,System.Func`1<System.Web.Mvc.ActionExecutedContext>) <IL 0x00031, 0x000b6>
--------------------------------------------------------------------------------
Version information: Mono Runtime Version: 2.10.2 (tarball Mon Apr 18 18:57:39 UTC 2011); ASP.NET Version: 2.0.50727.1433
Here is my approach. In MVC you will have an action called RetrievePassword where you will ask for the user's email address and pass it in a post
[HttpGet]
public ActionResult RetrievePassword()
{
return View();
}
[HttpPost]
public ActionResult RetrievePassword(PasswordRetrievalModel model)
{
if (ModelState.IsValid)
{
string username = Membership.GetUserNameByEmail(model.Email);
if (!String.IsNullOrEmpty(username))
{
// This is a helper function that sends an email with a token (an MD5).
NotificationsHelper.SendPasswordRetrieval(model.Email, this.ControllerContext);
}
else
{
Trace.WriteLine(String.Format("*** WARNING: A user tried to retrieve their password but the email address used '{0}' does not exist in the database.", model.Email));
}
return RedirectToAction("Index", "Home");
}
return View(model);
}
An email message will be sent with a url that redirects to http://example.com/Account/Validate?email=xxxxxxxx&token=xxxxxxxx
If the token is valid for the email, you will probably display a password reset form so they choose a new password.
So you need a Validate Action:
[HttpGet]
[CompressFilter]
public ActionResult Validate(string email, string token)
{
bool isValid = false;
if (AccountHelper.IsTokenValid(token, email))
{
string username = Membership.GetUserNameByEmail(email);
if (!String.IsNullOrEmpty(username))
{
// Get the user and approve it.
MembershipUser user = Membership.GetUser(username);
user.IsApproved = true;
Membership.UpdateUser(user);
isValid = true;
// Since it was a successful validation, authenticate the user.
FormsAuthentication.SetAuthCookie(username, false);
}
else
{
isValid = false;
}
}
return View(isValid);
}
Here are some of the helpers you see in this code:
Account Helper
/// <summary>
/// Gets the token for invitation.
/// </summary>
/// <param name="email">The email.</param>
/// <returns></returns>
public static string GetTokenForInvitation(string email)
{
if (String.IsNullOrEmpty(email))
throw new ArgumentException("The email cannot be null");
string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
return token;
}
/// <summary>
/// Gets the email from token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="email">The email.</param>
/// <returns></returns>
public static bool GetEmailFromToken(string token, out string email)
{
email = String.Empty;
string message = Password.DecodeMessageWithPassword(token, SEED);
string[] messageParts = message.Split('#');
if (messageParts.Count() != 2)
{
return false;
// the token was not generated correctly.
}
else
{
email = messageParts[0];
return true;
}
}
/// <summary>
/// Helper function used to generate a token to be used in the message sent to users when registered the first time to confirm their email address.
/// </summary>
/// <param name="email">The email address to encode.</param>
/// <returns>The token generated from the email address, timestamp, and SEED value.</returns>
public static string GetTokenForValidation(string email)
{
if (String.IsNullOrEmpty(email))
throw new ArgumentException("The email cannot be null");
string token = Password.EncodeMessageWithPassword(String.Format("{0}#{1}", email, DateTime.Now), SEED);
return token;
}
/// <summary>
/// Validates whether a given token is valid for a determined email address.
/// </summary>
/// <param name="token">The token to validate.</param>
/// <param name="email">The email address to use in the validation.</param>
/// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
public static bool IsTokenValid(string token, string email)
{
return IsTokenValid(token, email, DateTime.Now);
}
/// <summary>
/// Core method to validate a token that also offers a timestamp for testing. In production mode should always be DateTime.Now.
/// </summary>
/// <param name="token">The token to validate.</param>
/// <param name="email">the email address to use in the validation.</param>
/// <param name="timestamp">The timestamp representing the time in which the validation is performed.</param>
/// <returns><c>true</c> if the token is valid, <c>false</c> otherwise.</returns>
public static bool IsTokenValid(string token, string email, DateTime timestamp)
{
if (String.IsNullOrEmpty(token))
throw new ArgumentException("The token cannot be null");
try
{
string message = Password.DecodeMessageWithPassword(token, SEED);
string[] messageParts = message.Split('#');
if (messageParts.Count() != 2)
{
return false;
// the token was not generated correctly.
}
else
{
string messageEmail = messageParts[0];
string messageDate = messageParts[1];
// If the emails are the same and the date in which the token was created is no longer than 5 days, then it is valid. Otherwise, it is not.
return (String.Compare(email, messageEmail, true) == 0 && timestamp.Subtract(DateTime.Parse(messageDate)).Days < 5);
}
}
catch (Exception)
{
// could not decrypt the message. The token has been tampered with.
return false;
}
}
And Finally here some code to encrypt, decript a token...
I have it in a Password class that is intended to be a helper.
/// EDIT:
Removed the two functions I referenced before and show the full helper class.
Here is the Password static class with all helper functions.
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Data;
using System.Resources;
namespace MySolution.Common.Util
{
/// <summary>
/// Implements some functions to support password manipulation or generation
/// </summary>
public class Password
{
/// <summary>
/// Takes a string and generates a hash value of 16 bytes.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
/// <returns>A hex string of the hashed password.</returns>
public static string EncodeString(string str, string passwordFormat)
{
if (str == null)
return null;
ASCIIEncoding AE = new ASCIIEncoding();
byte[] result;
switch (passwordFormat)
{
case "sha1":
SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
result = sha1.ComputeHash(AE.GetBytes(str));
break;
case "md5":
MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
result = md5.ComputeHash(AE.GetBytes(str));
break;
default:
throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
}
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < result.Length; i++)
{
sb.Append(result[i].ToString("x2"));
}
return sb.ToString();
}
/// <summary>
/// Takes a string and generates a hash value of 16 bytes. Uses "md5" by default.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <returns>A hex string of the hashed password.</returns>
public static string EncodeString(string str)
{
return EncodeString(str, "md5");
}
/// <summary>
/// Takes a string and generates a hash value of 16 bytes.
/// </summary>
/// <param name="str">The string to be hashed</param>
/// <param name="passwordFormat">Selects the hashing algorithm used. Accepted values are "sha1" and "md5".</param>
/// <returns>A string of the hashed password.</returns>
public static string EncodeBinary(byte[] buffer, string passwordFormat)
{
if (buffer == null)
return null;
byte[] result;
switch (passwordFormat)
{
case "sha1":
SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
result = sha1.ComputeHash(buffer);
break;
case "md5":
MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
result = md5.ComputeHash(buffer);
break;
default:
throw new ArgumentException("Invalid format value. Accepted values are 'sha1' and 'md5'.", "passwordFormat");
}
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < result.Length; i++)
{
sb.Append(result[i].ToString("x2"));
}
return sb.ToString();
}
/// <summary>
/// Encodes the buffer using the default cryptographic provider.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns></returns>
public static string EncodeBinary(byte[] buffer)
{
return EncodeBinary(buffer, "md5");
}
/// <summary>
/// Creates a random alphanumeric password.
/// </summary>
/// <returns>A default length character string with the new password.</returns>
/// <remarks>The default length of the password is eight (8) characters.</remarks>
public static string CreateRandomPassword()
{
//Default length is 8 characters
return CreateRandomPassword(8);
}
/// <summary>
/// Creates a random alphanumeric password on dimension (Length).
/// </summary>
/// <param name="Length">The number of characters in the password</param>
/// <returns>The generated password</returns>
public static string CreateRandomPassword(int Length)
{
Random rnd = new Random(Convert.ToInt32(DateTime.Now.Millisecond)); //Creates the seed from the time
string Password="";
while (Password.Length < Length )
{
char newChar = Convert.ToChar((int)((122 - 48 + 1) * rnd.NextDouble() + 48));
if ((((int) newChar) >= ((int) 'A')) & (((int) newChar) <= ((int) 'Z')) | (((int) newChar) >= ((int) 'a')) & (((int) newChar) <= ((int) 'z')) | (((int) newChar) >= ((int) '0')) & (((int) newChar) <= ((int) '9')))
Password += newChar;
}
return Password;
}
/// <summary>
/// Takes a text message and encrypts it using a password as a key.
/// </summary>
/// <param name="plainMessage">A text to encrypt.</param>
/// <param name="password">The password to encrypt the message with.</param>
/// <returns>Encrypted string.</returns>
/// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
public static string EncodeMessageWithPassword(string plainMessage, string password)
{
if (plainMessage == null)
throw new ArgumentNullException("encryptedMessage", "The message cannot be null");
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.IV = new byte[8];
//Creates the key based on the password and stores it in a byte array.
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
MemoryStream ms = new MemoryStream(plainMessage.Length * 2);
CryptoStream encStream = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainMessage);
encStream.Write(plainBytes, 0, plainBytes.Length);
encStream.FlushFinalBlock();
byte[] encryptedBytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(encryptedBytes, 0, (int)ms.Length);
encStream.Close();
return Convert.ToBase64String(encryptedBytes);
}
/// <summary>
/// Takes an encrypted message using TripleDES and a password as a key and converts it to the original text message.
/// </summary>
/// <param name="encryptedMessage">The encrypted message to decode.</param>
/// <param name="password">The password to decode the message.</param>
/// <returns>The Decrypted message</returns>
/// <remarks>This method uses TripleDES symmmectric encryption.</remarks>
public static string DecodeMessageWithPassword(string encryptedMessage, string password)
{
if (encryptedMessage == null)
throw new ArgumentNullException("encryptedMessage", "The encrypted message cannot be null");
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.IV = new byte[8];
//Creates the key based on the password and stores it in a byte array.
PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[0]);
des.Key = pdb.CryptDeriveKey("RC2", "MD5", 128, new byte[8]);
//This line protects the + signs that get replaced by spaces when the parameter is not urlencoded when sent.
encryptedMessage = encryptedMessage.Replace(" ", "+");
MemoryStream ms = new MemoryStream(encryptedMessage.Length * 2);
CryptoStream decStream = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
byte[] plainBytes;
try
{
byte[] encBytes = Convert.FromBase64String(Convert.ToString(encryptedMessage));
decStream.Write(encBytes, 0, encBytes.Length);
decStream.FlushFinalBlock();
plainBytes = new byte[ms.Length];
ms.Position = 0;
ms.Read(plainBytes, 0, (int)ms.Length);
decStream.Close();
}
catch(CryptographicException e)
{
throw new ApplicationException("Cannot decrypt message. Possibly, the password is wrong", e);
}
return Encoding.UTF8.GetString(plainBytes);
}
}
}
Set a Reset password GUID in user table. You may also use an expiration time. If user tried to reset password, update the field with a new GUID and datetime for expiration.
Send a link containing the link to reset password with the GUID.
A sample function like this can be created for that
GUID res = objPasswordResetService.resetPassword(Convert.ToInt64(objUserViewModel.UserID), restpasswordGuid, resetPasswordExpiryDateTime);
The value in res can be the GUID updated in DB. Send a link with this GUID. You can check the expiration time also. This is just an idea only
I've got an example of how to implement password recovery in a standard ASP.NET MVC application in my blog.
This blog post assumes that you already have the login process working (database and all) and that you only need to wire the password recovery process.
http://hectorcorrea.com/Blog/Password-Recovery-in-an-ASP.NET-MVC-Project
Answer to implement password reset in MVC2 application
public string ResetPassword(string userName)
{
MembershipUser user = _provider.GetUser(userName, false);
if (user.IsLockedOut)
user.UnlockUser();
user.Comment = null;
_provider.UpdateUser(user);
string newPassword = user.ResetPassword();
string friendlyPassword = GenerateNewPassword();
_provider.ChangePassword(userName, newPassword, friendlyPassword);
return friendlyPassword;
}
private string GenerateNewPassword()
{
string strPwdchar = "abcdefghijklmnopqrstuvwxyz0123456789##$ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string strPwd = "";
Random rnd = new Random();
for (int i = 0; i <= 8; i++)
{
int iRandom = rnd.Next(0, strPwdchar.Length - 1);
strPwd += strPwdchar.Substring(iRandom, 1);
}
return strPwd;
}
here the russian version password recovery

MVCContrib Grid showing headers when empty?

The elegant Action Syntax in the MVCContrib Grid gives us the Empty() method. However, the default behavior of MvcContrib.UI.Grid.GridRenderer<T>.RenderHeader() is to hide the table column headers when the grid is empty. Is there a way to show headers when data is not present that does not require a major refactoring?
Now I have heard of hiding the headers by default and hard-coding something but this is not cool to me.
By the way, this is what is happening under the hood (in MvcContrib.UI.Grid.GridRenderer<T>):
protected virtual bool RenderHeader()
{
//No items - do not render a header.
if(! ShouldRenderHeader()) return false;
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return true;
}
protected virtual bool ShouldRenderHeader()
{
return !IsDataSourceEmpty();
}
protected bool IsDataSourceEmpty()
{
return DataSource == null || !DataSource.Any();
}
You can override the ShouldRenderHeader() method of the HtmlTableGridRenderer class.
public class AlwaysRenderHeaderRenderer<T>
: HtmlTableGridRenderer<T> where T : class
{
protected override bool ShouldRenderHeader()
{
return true;
}
}
<%= Html.Grid(Model).RenderUsing(new AlwaysRenderHeaderRenderer<TypeOfModel>()) %>
A side effect of this approach is that when the grid is empty, an empty table body will be rendered instead of a message. Any text provided to Empty() is ignored. This wasn't a problem for me since I'm manipulating the table on the client side with JavaScript anyway, but be warned.
When inheriting from HtmlTableGridRenderer you can also override RenderEmpty to eliminate the problem Brant ran into.
protected override void RenderEmpty()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
RenderHeadEnd();
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}
In your custom Grid Renderer (subclass GridRenderer<T>) use overrides like these:
/// <summary>
/// Renders the <c>table</c> header.
/// </summary>
/// <returns>
/// Returns the negative of the results
/// of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>.
/// </returns>
/// <remarks>
/// The return value of <see cref="GridRenderer<T>.IsDataSourceEmpty"/>
/// in this override has no effect on whether the Grid header is rendered.
///
/// However, this return value is used
/// by <see cref="GridRenderer<T>.Render"/>
/// to run <see cref="GridRenderer<T>.RenderItems"/>
/// or <see cref="GridRenderer<T>.RenderEmpty"/>.
/// </remarks>
protected override bool RenderHeader()
{
RenderHeadStart();
foreach(var column in VisibleColumns())
{
//Allow for custom header overrides.
if(column.CustomHeaderRenderer != null)
{
column.CustomHeaderRenderer(new RenderingContext(Writer, Context, _engines));
}
else
{
RenderHeaderCellStart(column);
RenderHeaderText(column);
RenderHeaderCellEnd();
}
}
RenderHeadEnd();
return !base.IsDataSourceEmpty();
}
…
protected override void RenderEmpty()
{
RenderBodyStart();
WriteNoRecordsAvailable(base.Writer, this._numberOfTableColumns);
RenderBodyEnd();
}
Note that WriteNoRecordsAvailable() is my custom method so it can be ignored.
Finally:
/// <summary>
/// This private member is duplicated
/// in order to override <see cref="GridRenderer<T>.RenderHeader"/>.
/// </summary>
readonly ViewEngineCollection _engines;
…
/// <summary>
/// Initializes a new instance of the <see cref="CrmHtmlTableGridRenderer<T>"/> class.
/// </summary>
/// <param name="engines">The engines.</param>
public CrmHtmlTableGridRenderer(ViewEngineCollection engines)
: base(engines)
{
_engines = engines;
}
Can't you just comment out the check to see if it should render the header. Am I missing something or do you want it to always display the header?
If so then comment out that line.
//if(! ShouldRenderHeader()) return false;
I haven't looks at all the code but from your code snippet it seems like that should work.
A combination of David's and Brant's answers:
protected override bool ShouldRenderHeader()
{
return true;
}
// Oddly Render relies on ShouldRenderHeader to return IsDataSourceEmpty
// so RenderItems will always be called.
protected override void RenderItems()
{
if (IsDataSourceEmpty())
RenderEmpty();
else
base.RenderItems();
}
protected override void RenderEmpty()
{
RenderBodyStart();
RenderText("<tr><td colspan=\"" + VisibleColumns().Count() + "\">" + GridModel.EmptyText + "</td></tr>");
RenderBodyEnd();
}

Profile System: User share the same id

I have a strange effect on my site when it is under heavy load.
I randomly get the properties of other users settings. I have my own implementation of the profile system so I guess I can not blame the profile system itself.
I just need a point to start debugging from. I guess there is a cookie-value that maps to an Profile entry somewhere. Is there any chance to see how this mapping works?
Here is my profile provider:
using System;
using System.Text;
using System.Configuration;
using System.Web;
using System.Web.Profile;
using System.Collections;
using System.Collections.Specialized;
using B2CShop.Model;
using log4net;
using System.Collections.Generic;
using System.Diagnostics;
using B2CShop.DAL;
using B2CShop.Model.RepositoryInterfaces;
[assembly: log4net.Config.XmlConfigurator()]
namespace B2CShop.Profile
{
public class B2CShopProfileProvider : ProfileProvider
{
private static readonly ILog _log = LogManager.GetLogger(typeof(B2CShopProfileProvider));
// Get an instance of the Profile DAL using the ProfileDALFactory
private static readonly B2CShop.DAL.UserRepository dal = new B2CShop.DAL.UserRepository();
// Private members
private const string ERR_INVALID_PARAMETER = "Invalid Profile parameter:";
private const string PROFILE_USER = "User";
private static string applicationName = B2CShop.Model.Configuration.ApplicationConfiguration.MembershipApplicationName;
/// <summary>
/// The name of the application using the custom profile provider.
/// </summary>
public override string ApplicationName
{
get
{
return applicationName;
}
set
{
applicationName = value;
}
}
/// <summary>
/// Initializes the provider.
/// </summary>
/// <param name="name">The friendly name of the provider.</param>
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
throw new ArgumentNullException("config");
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "B2C Shop Custom Provider");
}
if (string.IsNullOrEmpty(name))
name = "b2c_shop";
if (config["applicationName"] != null && !string.IsNullOrEmpty(config["applicationName"].Trim()))
applicationName = config["applicationName"];
base.Initialize(name, config);
}
/// <summary>
/// Returns the collection of settings property values for the specified application instance and settings property group.
/// </summary>
/// <param name="context">A System.Configuration.SettingsContext describing the current application use.</param>
/// <param name="collection">A System.Configuration.SettingsPropertyCollection containing the settings property group whose values are to be retrieved.</param>
/// <returns>A System.Configuration.SettingsPropertyValueCollection containing the values for the specified settings property group.</returns>
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
{
string username = (string)context["UserName"];
bool isAuthenticated = (bool)context["IsAuthenticated"];
//if (!isAuthenticated) return null;
int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();
foreach (SettingsProperty prop in collection)
{
SettingsPropertyValue pv = new SettingsPropertyValue(prop);
switch (pv.Property.Name)
{
case PROFILE_USER:
if (!String.IsNullOrEmpty(username))
{
pv.PropertyValue = GetUser(uniqueID);
}
break;
default:
throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
}
svc.Add(pv);
}
return svc;
}
/// <summary>
/// Sets the values of the specified group of property settings.
/// </summary>
/// <param name="context">A System.Configuration.SettingsContext describing the current application usage.</param>
/// <param name="collection">A System.Configuration.SettingsPropertyValueCollection representing the group of property settings to set.</param>
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
string username = (string)context["UserName"];
CheckUserName(username);
bool isAuthenticated = (bool)context["IsAuthenticated"];
int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
if (uniqueID == 0)
{
uniqueID = dal.CreateProfileForUser(username, isAuthenticated, ApplicationName);
}
foreach (SettingsPropertyValue pv in collection)
{
if (pv.PropertyValue != null)
{
switch (pv.Property.Name)
{
case PROFILE_USER:
SetUser(uniqueID, (UserInfo)pv.PropertyValue);
break;
default:
throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
}
}
}
UpdateActivityDates(username, false);
}
// Profile gettters
// Retrieve UserInfo
private static UserInfo GetUser(int userID)
{
return dal.GetUser(userID);
}
// Update account info
private static void SetUser(int uniqueID, UserInfo user)
{
user.UserID = uniqueID;
dal.SetUser(user);
}
// UpdateActivityDates
// Updates the LastActivityDate and LastUpdatedDate values
// when profile properties are accessed by the
// GetPropertyValues and SetPropertyValues methods.
// Passing true as the activityOnly parameter will update
// only the LastActivityDate.
private static void UpdateActivityDates(string username, bool activityOnly)
{
dal.UpdateActivityDates(username, activityOnly, applicationName);
}
/// <summary>
/// Deletes profile properties and information for the supplied list of profiles.
/// </summary>
/// <param name="profiles">A System.Web.Profile.ProfileInfoCollection of information about profiles that are to be deleted.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteProfiles(ProfileInfoCollection profiles)
{
int deleteCount = 0;
foreach (ProfileInfo p in profiles)
if (DeleteProfile(p.UserName))
deleteCount++;
return deleteCount;
}
/// <summary>
/// Deletes profile properties and information for profiles that match the supplied list of user names.
/// </summary>
/// <param name="usernames">A string array of user names for profiles to be deleted.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteProfiles(string[] usernames)
{
int deleteCount = 0;
foreach (string user in usernames)
if (DeleteProfile(user))
deleteCount++;
return deleteCount;
}
// DeleteProfile
// Deletes profile data from the database for the specified user name.
private static bool DeleteProfile(string username)
{
CheckUserName(username);
return dal.DeleteAnonymousProfile(username, applicationName);
}
// Verifies user name for sise and comma
private static void CheckUserName(string userName)
{
if (string.IsNullOrEmpty(userName) || userName.Length > 256 || userName.IndexOf(",") > 0)
throw new ApplicationException(ERR_INVALID_PARAMETER + " user name.");
}
/// <summary>
/// Deletes all user-profile data for profiles in which the last activity date occurred before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are deleted.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <returns>The number of profiles deleted from the data source.</returns>
public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
string[] userArray = new string[0];
dal.GetInactiveProfiles((int)authenticationOption, userInactiveSinceDate, ApplicationName).CopyTo(userArray, 0);
return DeleteProfiles(userArray);
}
/// <summary>
/// Retrieves profile information for profiles in which the user name matches the specified user names.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information
// for profiles where the user name matches the supplied usernameToMatch parameter.</returns>
public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, usernameToMatch, null, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves profile information for profiles in which the last activity date occurred on or before the specified date and the user name matches the specified user name.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="usernameToMatch">The user name to search for.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user profile information for inactive profiles where the user name matches the supplied usernameToMatch parameter.</returns>
public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, usernameToMatch, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves user profile data for all profiles in the data source.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information for all profiles in the data source.</returns>
public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, null, null, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Retrieves user-profile data from the data source for profiles in which the last activity date occurred on or before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <param name="pageIndex">The index of the page of results to return.</param>
/// <param name="pageSize">The size of the page of results to return.</param>
/// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
/// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information about the inactive profiles.</returns>
public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
CheckParameters(pageIndex, pageSize);
return GetProfileInfo(authenticationOption, null, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
}
/// <summary>
/// Returns the number of profiles in which the last activity date occurred on or before the specified date.
/// </summary>
/// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
/// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
/// <returns>The number of profiles in which the last activity date occurred on or before the specified date.</returns>
public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
{
int inactiveProfiles = 0;
ProfileInfoCollection profiles = GetProfileInfo(authenticationOption, null, userInactiveSinceDate, 0, 0, out inactiveProfiles);
return inactiveProfiles;
}
//Verifies input parameters for page size and page index.
private static void CheckParameters(int pageIndex, int pageSize)
{
if (pageIndex < 1 || pageSize < 1)
throw new ApplicationException(ERR_INVALID_PARAMETER + " page index.");
}
//GetProfileInfo
//Retrieves a count of profiles and creates a
//ProfileInfoCollection from the profile data in the
//database. Called by GetAllProfiles, GetAllInactiveProfiles,
//FindProfilesByUserName, FindInactiveProfilesByUserName,
//and GetNumberOfInactiveProfiles.
//Specifying a pageIndex of 0 retrieves a count of the results only.
private static ProfileInfoCollection GetProfileInfo(ProfileAuthenticationOption authenticationOption, string usernameToMatch, object userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
{
ProfileInfoCollection profiles = new ProfileInfoCollection();
totalRecords = 0;
// Count profiles only.
if (pageSize == 0)
return profiles;
int counter = 0;
int startIndex = pageSize * (pageIndex - 1);
int endIndex = startIndex + pageSize - 1;
DateTime dt = new DateTime(1900, 1, 1);
if (userInactiveSinceDate != null)
dt = (DateTime)userInactiveSinceDate;
/*
foreach(CustomProfileInfo profile in dal.GetProfileInfo((int)authenticationOption, usernameToMatch, dt, applicationName, out totalRecords)) {
if(counter >= startIndex) {
ProfileInfo p = new ProfileInfo(profile.UserName, profile.IsAnonymous, profile.LastActivityDate, profile.LastUpdatedDate, 0);
profiles.Add(p);
}
if(counter >= endIndex) {
break;
}
counter++;
}
*/
return profiles;
}
}
}
This is how I use it in the controller:
public ActionResult AddTyreToCart(CartViewModel model)
{
string profile = Request.IsAuthenticated ? Request.AnonymousID : User.Identity.Name;
}
I would like to debug: How can 2 users who provide different cookies get the same profileid?
EDIT
Here is the code for getuniqueid
public int GetUniqueID(string userName, bool isAuthenticated, bool ignoreAuthenticationType, string appName)
{
SqlParameter[] parms = {
new SqlParameter("#Username", SqlDbType.VarChar, 256),
new SqlParameter("#ApplicationName", SqlDbType.VarChar, 256)};
parms[0].Value = userName;
parms[1].Value = appName;
if (!ignoreAuthenticationType)
{
Array.Resize(ref parms, parms.Length + 1);
parms[2] = new SqlParameter("#IsAnonymous", SqlDbType.Bit) { Value = !isAuthenticated };
}
int userID;
object retVal = null;
retVal = SqlHelper.ExecuteScalar(ConfigurationManager.ConnectionStrings["SQLOrderB2CConnString"].ConnectionString, CommandType.StoredProcedure, "getProfileUniqueID", parms);
if (retVal == null)
userID = CreateProfileForUser(userName, isAuthenticated, appName);
else
userID = Convert.ToInt32(retVal);
return userID;
}
And this is the SP:
CREATE PROCEDURE [dbo].[getProfileUniqueID]
#Username VarChar( 256),
#ApplicationName VarChar( 256),
#IsAnonymous bit = null
AS
BEGIN
SET NOCOUNT ON;
/*
[getProfileUniqueID]
created
08.07.2009 mf
Retrive unique id for current user
*/
SELECT UniqueID FROM dbo.Profiles WHERE Username = #Username
AND ApplicationName = #ApplicationName
AND IsAnonymous = #IsAnonymous or #IsAnonymous = null
END
I haven't looked at this in depth, but one thing did jump out at me.
In your where clause change
#IsAnonymous = null
to
#IsAnonymous IS NULL
What do you want to happen when #IsAnonymous is null? You might need to add a few parenthesis to the where clause.
SELECT UniqueID FROM dbo.Profiles
WHERE Username = #Username
AND ApplicationName = #ApplicationName
AND (IsAnonymous = #IsAnonymous OR #IsAnonymous IS NULL)
Do you two users who received the same profile data? If so start there. I would run them through the stored proc first. It would be easy and quick to do.
This is a very difficult type of issue to troubleshoot. We had a similar issue in an ASP.NET app (not MVC). I'd suggest that you can start by setting a separate cookie to identify the user, completely bypassing the ASP.NET profile system. You can then check whether the identity in that cookie matches the identity in the profile. If it doesn't then you can 1) log out the user so at least they don't get access to someone else's data and 2) gather some diagnostic information - the complete HTTP request details would be a start. If that doesn't establish any sort of pattern you could start logging all HTTP requests by user to see if there's a pattern in their request history that helps you to reproduce the problem.
In our case the issue ended up being a silly home-grown caching mechanism. The application would try to cache static files (eg. images) in ASP.NET and cookies got cached with them. Under heavy load a user would sometimes load an image with another user's cookies and assume the other user's identity. Your case may be completely different, but this might give you some clue of the type of thing to look for.
The given sources seem to be okay, but i cannot make a profound statement without haveing all the corresponding sourcecode (eg. all used methods of class B2CShop.DAL.UserRepository), perhaps even the database scheme.
First would be to check if perhaps the data in the database is corrupted, so to say that some user may literally own the same unique id or the same username/application/isanonymous combination. This can be excluded if uniqueid is a primary key.
Whenever these kinds of issue happen... I have seen the following to be the causes...
Cache
Static variables
Can you try to write this code without static variables and check if the issue resolves?
Regards,
Rahul

Resources