Wednesday, December 19, 2012

Best Practices for Asp.net MVC

Bookmark of few good asp.net mvc best practices links for my future reference:


Note: I will be updating with my list added on top of this later

I have arrange them in order in which i enjoyed reading/viewing :)

Securing Asp.net Application

Incase if you haven't viewed this video which is talking on basic and very important aspect of securing your asp.net application, then I will strongly recommend everyone to view this video.

Tuesday, December 11, 2012

AllowHtml Attribute to allow html for your asp.net mvc application

Whenever you are trying to take html input from your asp.net mvc application without using AllowHtml attribute to model field then you will run into following error.

Server Error in '/' Application.

A potentially dangerous Request.Form value was detected from the client (StepValue="...Enumerable intSequence = ...").

Description: ASP.NET has detected data in the request that is potentially dangerous because it might include HTML markup or script. The data might represent an attempt to compromise the security of your application, such as a cross-site scripting attack. If this type of input is appropriate in your application, you can include code in a web page to explicitly allow it. For more information, see http://go.microsoft.com/fwlink/?LinkID=212874.

Exception Details: System.Web.HttpRequestValidationException: A potentially dangerous Request.Form value was detected from the client (StepValue="...Enumerable intSequence = ...").

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:


[HttpRequestValidationException (0x80004005): A potentially dangerous Request.Form value was detected from the client (StepValue="...Enumerable intSequence = ...").]
   System.Web.HttpRequest.ValidateString(String value, String collectionKey, RequestValidationSource requestCollection) +9665149
   System.Web.<>c__DisplayClass5.b__3(String key, String value) +18
   System.Web.HttpValueCollection.EnsureKeyValidated(String key) +9664565



Following are two most popular ways to allow html input in asp.net mvc

  • Make use of [AllowHtml] attribute on model field you want to allow html input. (Recommended way)
  • Make use of [ValidateInput(false)] on controller method - non recommended way because it will not validate any input field.


Example

Creating BlogPost wherein we want to allow blog content to take html

Model class 
public class BlogPost {
    public string Title { get; set; }
    public DateTime PostedOn { get; set; }
    public string Tags { get; set; }
    public string Content { get; set; }
}

Controller class
public class BlogPostController : Controller {
        public ActionResult Create() { 
            return View();
        }
        [HttpPost]
        public ActionResult Create(BlogPost model) {
            ViewBag.HtmlContent = model.Content; 
            return View(model);
        }
    }

View Page

@using (Html.BeginForm()) {
   
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>BlogPost</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.PostedOn)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PostedOn)
            @Html.ValidationMessageFor(model => model.PostedOn)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Tags)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Tags)
            @Html.ValidationMessageFor(model => model.Tags)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Content)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Content)
            @Html.ValidationMessageFor(model => model.Content)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>

        <p>
            Posted Content : @ViewBag.HtmlContent
        </p>

    </fieldset>
}



Method 1: Example with [AllowHtml] Attribute (Recommended)
By allowing html using AllowHtml Attribute we are limiting Html input for particular fields.  In our example we are allowing html to only BlogPost - Content field.

Change your BlogPost Model and add [AllowHtml] attribute as follow:
using System.Web.Mvc;

public class BlogPost {
    public string Title { get; set; }
    public DateTime PostedOn { get; set; }
    public string Tags { get; set; }
  
    [AllowHtml]
    public string Content { get; set; }
}


Method 2: Example with [ValidateInput(false)] (Non-Recommended)
You can also allow html by simply turning off validation on controller method as shown in following example:


public class BlogPostController : Controller {
        public ActionResult Create() { 
            return View();
        }
        [HttpPost]
        [ValidateInput(false)]
        public ActionResult Create(BlogPost model) {
            ViewBag.HtmlContent = model.Content; 
            return View(model);
        }
    }

Problem with using [ValidateInput(false)] is it will turn off validation on whole controller's action method which is very dangerous.

Monday, December 10, 2012

Weird errors in asp.net mvc and its solution

If you are experiencing weird errors in asp.net mvc.

Situation 1
You have added namespace in Web.config file still your View Page is not reflecting any change or still control is error-ed out, then close the view and reopen it.  Yes VS.Net requires closing and opening of view page in order to reflect changes.


Situation 2
You have made major change in Entity Framework (Code First) Entity class and even though making sure that all changes are properly applied if you receive any runtime error saying "Invalid column name" or may be something as shown below:  Then simply close your asp.net mvc project (VS.Net Solution) and open it again. (Yes i found this weird because it requires me to close my vs.net solution and reopen it again to reflect all changes, even though i tried clean solution and rebuild solution before running project)


 at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
   at System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute[TResultType](ObjectContext context, ObjectParameterCollection parameterValues)
   at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
   at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable.GetEnumerator()
   at System.Data.Entity.Internal.Linq.InternalQuery`1.GetEnumerator()
   at System.Data.Entity.Infrastructure.DbQuery`1.System.Collections.Generic.IEnumerable.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at MyBlog.Controllers.StepbystepController.Index() in d:\Websites\Source\MyBlog\MyBlog\Controllers\StepbystepController.cs:line 27
   at lambda_method(Closure , ControllerBase , Object[] )
   at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
   at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.b__41()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.b__7(IAsyncResult _)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.b__33()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.b__49()





Situation 3
You have change model in view then to apply changes you need to close view and reopen it again.


Now below situation are something becoz of our mistake but we don't generally noticed.  Mostly this errors result in 404 - Error Page.

If you are getting 404 Error in asp.net mvc then make sure
1) Check action name and then check view name.
Example: You have created "ContactMessage" action method in Home controller and you have created "Contact" view page, this mismatch will result in 404 error.  I know you new this but you don't realize this when 404 occurs.

2) Check whether you have created view page under correct directory name.  I will not go deep into Asp.net MVC Hierarchy structure but as you know that proper naming convention needs to follow, so by mistake if you have created view under wrong directory that will also results in 404 error page.

3) Check whether controls on your view is not error-ed out. (They are getting proper values assigned from model).

4) You can always turn CustomErrors mode="RemoteOnly" to simply attack problem at first.




There are few other situations but i can't able to recall them all, but you get the gist of my post.


Moral of story:  All errors mentioned here are either becoz of our mistake or vs.net, but we don't generally noticed, so if you run into similar situation then answer is relax and try to make sure that things are not becoz of your mistake before jumping into find solution.

Sunday, December 02, 2012

How to store millions of Images in best possible way in Asp.net

I have requirement in which i want to create a architecture to store millions of images in best possible way.

Requirement 1:
Each Image have its Unique name and Path by generating MD5 Hash code for Image.  By generating MD5 Hash code duplicating of Image can be avoided.
Example: If 100 user upload the exact same image, you will only keep one copy of that image on your filesystem instead of 100 copies.
Example of MD5 Hashcode from Image: 8b68925628110278bf194cbe5e071654


Requirement 2:
Making image storing pattern such that to keep directory sizes manageable.
Example of directory structure form from Image Hash Code.
/userimages/orig/8/b6/892/8b68925628110278bf194cbe5e071654.jpg
/userimages/orig/5/28/533/528533467df9388eb63c99e78f4e9499.jpg
/userimages/orig/d/34/ca1/d34ca13a7295ccc8461281e3e7567b9f.jpg
            
/userimages/medium/8/b6/892/8b68925628110278bf194cbe5e071654.jpg
/userimages/medium/5/28/533/528533467df9388eb63c99e78f4e9499.jpg
/userimages/medium/d/34/ca1/d34ca13a7295ccc8461281e3e7567b9f.jpg
                      
/userimages/small/8/b6/892/8b68925628110278bf194cbe5e071654.jpg
/userimages/small/5/28/533/528533467df9388eb63c99e78f4e9499.jpg
/userimages/small/d/34/ca1/d34ca13a7295ccc8461281e3e7567b9f.jpg

Please note: This article only discuss core concept about how to generate hash code for image and how to store image by creating proper directory structure while storing on file system.  You can add lot on top of this but this will be a good starting point.



protected void btnUploadImage_Click(object sender, EventArgs e)
{
            UploadImage();
}

private void UploadImage()
{
            if (!(FileUpload1.HasFile))
            {
                //Image Is Invalid
                //Reason: Either of following
                //1) Non-supported Image File Format
                //2) No Image File Found
                //3) Invalid image (Eg: Text file is renamed to .jpg Image)
                //Display appropriate message to user               
                //Please note I have NOT added code for how to validate image
                //In order to keep article focus on core concept
                return;
            }
            else
            {
                //Valid Image
                string ImgHashCode = Image2Md5Hash();
                lblImageHashCode.Text = ImgHashCode;

                //Todo: Write Code to Save HashCode.filetype in database

                //Save File on FileSystem
                SaveImageonFileSystem(ImgHashCode);
            }
}


Answer for Requirement 1:
//Image to MD5 Hash Code - Generating Hash Code for Image
private string Image2Md5Hash()
{           
            const int BUFFER_SIZE = 255;
            Byte[] Buffer = new Byte[BUFFER_SIZE];

            Stream theStream = FileUpload1.PostedFile.InputStream;
            int nBytesRead = theStream.Read(Buffer, 0, BUFFER_SIZE);
                        return CalculateMD5(theStream);
}

private static byte[] _emptyBuffer = new byte[0];

public static string CalculateMD5(Stream stream)
{
            return CalculateMD5(stream, 64 * 1024);
}

public static string CalculateMD5(Stream stream, int bufferSize)
{
            MD5 md5Hasher = MD5.Create();

            byte[] buffer = new byte[bufferSize];
            int readBytes;

            while ((readBytes = stream.Read(buffer, 0, bufferSize)) > 0)
            {
                md5Hasher.TransformBlock(buffer, 0, readBytes, buffer, 0);
            }

            md5Hasher.TransformFinalBlock(_emptyBuffer, 0, 0);

            var sb = new StringBuilder();
            foreach (byte b in md5Hasher.Hash)
                sb.Append(b.ToString("x2").ToLower());
            return sb.ToString();           
}


Answer for Requirement 2:
private void SaveImageonFileSystem(string ImgHashCode)
{
 /*
/userimages/orig/8/b6/892/8b68925628110278bf194cbe5e071654.jpg
/userimages/orig/5/28/533/528533467df9388eb63c99e78f4e9499.jpg
/userimages/orig/d/34/ca1/d34ca13a7295ccc8461281e3e7567b9f.jpg            
*/

            if(!string.IsNullOrEmpty(ImgHashCode))
            {
                string RootDirPath = "userimages";
                string ImageSize = "orig";
                string FirstFolder = ImgHashCode.Substring(0,1);
                string SecondFolder = ImgHashCode.Substring(1,2);
                string ThirdFolder = ImgHashCode.Substring(3, 3);

                string DirectoryName = Server.MapPath("~")
                                        + RootDirPath + "\\"
                                        + ImageSize + "\\"
                                        + FirstFolder + "\\"
                                        + SecondFolder + "\\"
                                        + ThirdFolder + "\\";

                if (!Directory.Exists(DirectoryName))
                {
                    Directory.CreateDirectory(DirectoryName);
                }

                FileUpload1.SaveAs(DirectoryName + ImgHashCode + Path.GetExtension(FileUpload1.FileName));
            }
}


Related Post
How to identify whether uploaded image is valid or not
How to remove image from cache

Most Recent Post

Subscribe Blog via Email

Enter your email address:



Disclaimers:We have tried hard to provide accurate information, as a user, you agree that you bear sole responsibility for your own decisions to use any programs, documents, source code, tips, articles or any other information provided on this Blog.
Page copy protected against web site content infringement by Copyscape