четверг, 23 февраля 2012 г.

AJAX form in ASP.NET MVC 3 for editing grid items




Recently, on one of the projects I had to do the edit form on ASP.NET MVC 3 so that saving data should happen asynchronously without reloading the page. To do this in ASP.NET MVC 3, there exists a function Ajax.BeginForm, which is behind the scenes uses a library jquery.unobtrusive-ajax.js
 
A good post on this topic before on this
blog. But there is editing in a modal window. I decided to show an example of editing the entries from the list on the same page, but without postback.

For cosmetic purposes, I use a jQuery plugin that allows you to use a busy indicator in the container form.






So let us have a model:
public class PersonModel
{
    [Key]
    public int Id { get; set; }

    [Required(ErrorMessage = "Required field")]
    [StringLength(10, MinimumLength = 3)]
    public string Name { get; set; }

    [Required]
    public string Email { get; set; }

    public int Age { get; set; }

    public IEnumerable<PersonModel> Persons { get; set; }
}

Let`s create partial view EditPerson for asynchronous editing each person object. Let`s use Ajax.BeginForm for that:

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<h2>Edit Person @Model.Name</h2>

@using (Ajax.BeginForm("EditPerson", new AjaxOptions { OnSuccess = "onSuccess", OnBegin = "onBegin", OnFailure = "handleError" }))
{
    @Html.HiddenFor(model => model.Id)

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

    <div class="editor-label">Email</div>
    <div class="editor-field">@Html.EditorFor(model => model.Email)</div>
        
    <div class="editor-label">Age</div>
    <div class="editor-field">@Html.EditorFor(model => model.Age)</div>
    
    <br/><hr/><br/>
    
    <input type="submit" value="Save" />
}

Of course, in order to intercept Ajax Submitting to controller, our action needs [HttpPost] attribute. In order to simply load up this form (for example, in some div) we need action without attribute also:

public class HomeController : Controller
{
    ...
    public ActionResult EditPerson(int id)
    {
        Thread.Sleep(1000);

        return View(this.GetPersons().FirstOrDefault(x => x.Id == id));
    }

    [HttpPost]
    public ActionResult EditPerson(PersonModel personModel)
    {
        if (ModelState.IsValid)
        {
            try
            {
                Thread.Sleep(1000);
                // todo: save person
            }
            catch (Exception ex)
            {
                Response.StatusCode = (int)HttpStatusCode.BadRequest;
                return this.Content(ex.Message);
            }
        }

        return View("SaveComplete", personModel);
    }
    ...
}

In order to submitting occurred asynchronously, you must use the library jquery.unobtrusive-ajax, we plugging above. Submit ready.

Now let`s make the view that will be display a list of persons. When you click on the Edit button next to each entry, form for particular person will be loaded into container div. View, which displays a list of persons looks like this:

@model test.Models.PersonModel
@{
    ViewBag.Title = "Persons";
}

<h2>Index</h2>

<div>
    <table>
        @foreach (var item in @Model.Persons) {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                    @Html.ValidationMessageFor(modelItem => modelItem.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Email)
                    @Html.ValidationMessageFor(modelItem => modelItem.Email)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Age)
                </td>
                <td>
                    <button onclick="@string.Format("editPerson({0})", item.Id)">Edit</button>
                </td>
            </tr>
        }
    </table>
</div>

<hr />

<div id="container">

</div>
and method for HomeController which returns a list of persons:
public class HomeController : Controller
{
    ...

private IEnumerable<PersonModel> GetPersons()
    {
        return new List<PersonModel>
        {
            new PersonModel {Id = 1, Age = 21, Email = "1@mail.com", Name = "Person 1"},
            new PersonModel {Id = 2, Age = 22, Email = "2@mail.com", Name = "Person 2"},
            new PersonModel {Id = 3, Age = 23, Email = "3@mail.com", Name = "Person 3"}
        };
    }
... 
}

Another view which we call SaveComplete, will show the successful preservation of person:

@model test.Models.PersonModel

@{
    ViewBag.Title = "SaveComplete";
    Layout = null;
}

<h2>@Model.Name successfully saved</h2>

Finally, in order to make it work, you must define handlers on javascript, which will load our form editing when you click on the button next to the record. It is also necessary to define handlers onSuccess, onBegin and handleError, which are indicated on EditPerson view for error handling and display busy indicator. Let`s do it all on the Index view:

<script src="@Url.Content("~/Scripts/jquery.busy.min.js")" type="text/javascript"></script>
<script type="text/javascript">
    function editPerson(personId) {
        $('#container').busy();
        if (personId == null) {
            return;
        }

        var editPersonUrl = '@Url.Action("EditPerson", new { id = "PersonIdPlaceholder", isNew = false })';
        var editPersonUrlWithNotatId = editPersonUrl.replace('PersonIdPlaceholder', personId);
        editPersonUrlWithNotatId = editPersonUrlWithNotatId.replace(/&amp;/g, '&');
        $('#container').load(editPersonUrlWithNotatId, function () {
            $('#container').busy("hide");
        });
    }

    function onBegin() {
        $('#container').busy("busy");
    }

    function onSuccess(context) {
        $('#container').html(context);
        $('#container').busy("hide");
    }

    function handleError(response) {
        $('#container').html("Save error:" + response.responseText + "<br>Status code: " + response.status);
        $('#container').busy("hide");
    }
</script>

Link to full solution source code

вторник, 7 февраля 2012 г.

Silverlight RIA Services Domain Context in offline mode

 Recently, my colleague asked me a question, is there a possibility to work with RIA Service context on the client in offline mode (when network connection is lost)? That is, when there is no connection between the client and the server machine. The reasons for this are numerous. Well, for example, when user works with his data, network fails and internet/intranet became unavailable. Or for example, a user working on a mobile device, went on a fishing trip, but forgot to pay for the internet. Of course, to work on fishing trip, come home and syncronize all of his data by simple click, is not that nice?

Searching the internet, you can find a project that offers a library "RisServiceContrib". It, in turn, contains extenders for exports and imports EntitySet to IsolatedStorage. I tried it, and suprisingly, it works fine :)

I decided to gently ease the task of saving and load data by this library.

For exporting data:

public static void ExportToStorage(this IEnumerable collection, string fileName) where T : Entity, new()
{
    using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (var isfs = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, isf))
        {
            var serializer = new DataContractSerializer(typeof(List));
            serializer.WriteObject(isfs, collection.Export());
            isfs.Close();
        }
    }
}
For importing data:


public static void ImportFromStorage(this EntitySet collection, string fileName) where T : Entity, new()
{
    using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (var isfs = new IsolatedStorageFileStream(fileName, FileMode.Open, isf))
        {
            var serializer = new DataContractSerializer(typeof(List));
            var list = (List)serializer.ReadObject(isfs);
            collection.Import(list);
            isfs.Close();
        }
    }
}
Use it like this:


public class MyViewModel
{
    ...

    private bool isNetworkAvailable;

    private void LoadEmployees()
    {
        var fileName = "cacheFile.name";
        if (isNetworkAvailable)
        {
            context.Load(context.GetEmployeesQuery(), LoadBehavior.RefreshCurrent, op => context.Employees.ExportToStorage(fileName), null);
        }
        else
        {
            context.Employees.ImportFromStorage(fileName);
        }
    }
    ...
}
 
If you are confused by the work of the flag isNetworkAvailable, just look at this post
Link to RiaServiceContrib dll 

среда, 1 февраля 2012 г.

Assembly+Deployment+Catalog (MEF). Get access to loaded assemblies.

Hi all.
I actively use MEF for Silverlight. Recently I got an interesting issue: load *.xap files dynamically to silverlight application. DeploymentCatalog can help us to make it work. When we add DeploymentCatalogs to AggregateCatalog, we can use it from AggregateCatalog of our service. Detail info you can get from this link 
But when I needed to get access to CLR types from xap assemblies (with reflection), I get in trouble - DeploymentCatalog encapsulates assemblies that has been loaded from xap file.
Of course I could use meta data with MEF`s Export attribute, but in this case I have to know beforehand about types that will be used out of xap file, which severely limits us. However, if we could get back a list of assemblies from DeploymentCatalog that was created from xap file, we were able to fully work through reflection with types.
So, pulling the code of DeploymentCatalog using reflector from MEF assemblies and adding new property Assemblies, which returns a list of assemblies from private property 'assemblies', I got a new class AssemblyDeploymentCatalog, which has DeploymentCatalog functionality with the ability to return the assemblies that were loaded. Use it!


    /// <summary>
    /// Repeats DeploymentCatalog(MEF) functionality with ability to get assemblies from composable parts
    /// </summary>
    public class AssemblyDeploymentCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
    {
        private Uri uri;
        private AggregateCatalog internalAggregateCatalog;
        private volatile bool disposed;
        private WebClient webClient;
        private int state;
        private IEnumerable<Assembly> assemblies;

        private readonly object synchHandle;

        private EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyDeploymentCatalog"/> class.
        /// </summary>
        /// <param name="uriRelative">The uri relative.</param>
        public AssemblyDeploymentCatalog(string uriRelative)
        {
            this.synchHandle = new object();

            this.disposed = false;
            this.state = 0;
            this.internalAggregateCatalog = new AggregateCatalog();
            this.webClient = null;
            if (string.IsNullOrEmpty(uriRelative))
            {
                throw new ArgumentNullException("uriRelative");
            }

            this.uri = new Uri(uriRelative, UriKind.Relative);
        }
        
        /// <summary>
        /// Fires on download complete
        /// </summary>
        public event EventHandler<AsyncCompletedEventArgs> DownloadCompleted;

        /// <summary>
        /// Fires after discovering parts
        /// </summary>
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changed;

        /// <summary>
        /// Fires while discovering parts
        /// </summary>
        public event EventHandler<ComposablePartCatalogChangeEventArgs> Changing;

        /// <summary>
        /// Gets Assemblies from catalog.
        /// </summary>
        public IEnumerable<Assembly> Assemblies
        {
            get { return this.assemblies; }
        }

        /// <summary>
        /// Gets composable parts.
        /// </summary>
        public override IQueryable<ComposablePartDefinition> Parts
        {
            get
            {
                this.ThrowIfDisposed();
                return this.internalAggregateCatalog.Parts;
            }
        }

        /// <summary>
        /// Gets current WebClient.
        /// </summary>
        private WebClient WebClient
        {
            get
            {
                this.ThrowIfDisposed();
                if (this.webClient == null)
                {
                    Interlocked.CompareExchange(ref this.webClient, new WebClient(), null);
                }

                return this.webClient;
            }
        }

        /// <summary>
        /// Gets current Uri.
        /// </summary>
        private Uri Uri
        {
            get
            {
                this.ThrowIfDisposed();
                return this.uri;
            }
        }

        /// <summary>
        /// Download package by URI asynchronously
        /// </summary>
        public void DownloadAsync()
        {
            this.ThrowIfDisposed();
            if (Interlocked.CompareExchange(ref this.state, 0x7d0, 0) == 0)
            {
                this.WebClient.OpenReadCompleted += this.HandleOpenReadCompleted;
                this.WebClient.DownloadProgressChanged += this.HandleDownloadProgressChanged;
                this.WebClient.OpenReadAsync(this.Uri, this);
            }
            else
            {
                this.MutateStateOrThrow(0xbb8, 0x3e8);
                this.OnDownloadCompleted(new AsyncCompletedEventArgs(null, false, this));
            }
        }

        /// <summary>Dispose Deployment Catalog</summary>
        /// <param name="disposing">The disposing.</param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing && !this.disposed)
                {
                    AggregateCatalog catalog = null;
                    try
                    {
                        lock (this.synchHandle)
                        {
                            if (!this.disposed)
                            {
                                catalog = this.internalAggregateCatalog;
                                this.internalAggregateCatalog = null;
                                this.disposed = true;
                            }
                        }
                    }
                    finally
                    {
                        if (catalog != null)
                        {
                            catalog.Dispose();
                        }
                    }
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        /// <summary>Changed event handler</summary>
        /// <param name="e">Event arguments</param>
        private void OnChanged(ComposablePartCatalogChangeEventArgs e)
        {
            var changed = this.Changed;
            if (changed != null)
            {
                changed(this, e);
            }
        }
        
        /// <summary>Changing event handler</summary>
        /// <param name="e">Event arguments</param>
        private void OnChanging(ComposablePartCatalogChangeEventArgs e)
        {
            var changing = this.Changing;
            if (changing != null)
            {
                changing(this, e);
            }
        }

        /// <summary>DownloadCompleted event handler</summary>
        /// <param name="e">Event arguments</param>
        private void OnDownloadCompleted(AsyncCompletedEventArgs e)
        {
            var downloadCompleted = this.DownloadCompleted;
            if (downloadCompleted != null)
            {
                downloadCompleted(this, e);
            }
        }

        /// <summary>OpenReadCompleted event handler</summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">Event arguments</param>
        private void HandleOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            var error = e.Error;
            var cancelled = e.Cancelled;
            if (Interlocked.CompareExchange(ref this.state, 0xbb8, 0x7d0) != 0x7d0)
            {
                cancelled = true;
            }

            if ((error == null) && !cancelled)
            {
                var downloadedStream = e.Result;

                this.assemblies = PackageUtility.LoadPackagedAssemblies(downloadedStream);
                this.DiscoverParts(this.assemblies);
            }

            this.OnDownloadCompleted(new AsyncCompletedEventArgs(error, cancelled, this));
        }

        /// <summary>DownloadProgressChanged event handler</summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">Event arguments</param>
        private void HandleDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            var downloadProgressChanged = this.DownloadProgressChanged;
            if (downloadProgressChanged != null)
            {
                downloadProgressChanged(this, e);
            }
        }

        private void MutateStateOrThrow(int toState, int fromState)
        {
            if (Interlocked.CompareExchange(ref this.state, toState, fromState) != fromState)
            {
                throw new InvalidOperationException("InvalidOperationException_DeploymentCatalogInvalidStateChange");
            }
        }

        /// <summary>Throws exception if object is disposed</summary>
        private void ThrowIfDisposed()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException(this.GetType().ToString());
            }
        }

        /// <summary>Discover all composable parts from package assemblies</summary>
        /// <param name="assemblies">The assemblies.</param>
        private void DiscoverParts(IEnumerable<Assembly> assemblies)
        {
            this.ThrowIfDisposed();
            var addedDefinitions = new List<ComposablePartDefinition>();
            var dictionary = new Dictionary<string, ComposablePartCatalog>();
            
            lock (this.synchHandle)
            {
                foreach (var assembly in assemblies)
                {
                    if (!dictionary.ContainsKey(assembly.FullName))
                    {
                        var catalog = new AssemblyCatalog(assembly);
                        addedDefinitions.AddRange(catalog.Parts);
                        dictionary.Add(assembly.FullName, catalog);
                    }
                }
            }

            using (var composition = new AtomicComposition())
            {
                var args = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty<ComposablePartDefinition>(), composition);
                this.OnChanging(args);

                lock (this.synchHandle)
                {
                    foreach (var pair in dictionary)
                    {
                        this.internalAggregateCatalog.Catalogs.Add(pair.Value);
                    }
                }

                composition.Complete();
            }

            var e = new ComposablePartCatalogChangeEventArgs(addedDefinitions, Enumerable.Empty<ComposablePartDefinition>(), null);
            this.OnChanged(e);
        }
    }
 
 
Now your class (service) contains AggregateCatalog, which in turn contains a list of Catalogs, some of which may be of the type AssemblyDeploymentCatalog, which means that you have access to all loaded assemblies.